Недавно я переехал из Пекина в Шанхай и начал новую жизнь, это большое событие в моей жизни, поэтому я специально использовал Three.js для визуализации.
В этом случае визуализации, связанной с географической информацией, мы можем научиться рисовать карту и преобразовывать широту и долготу в значения координат, что является общими методами географической визуализации.
Итак, приступим.
Анализ мыслей
Three.js рисует кубы, цилиндры и неправильные формы Мы все рисовали, но как нарисовать карту?
На самом деле карты тоже состоят из линий и полигонов.Имея данные мы можем их рисовать.Не хватает только данных.
Описание картографической информации является общим требованием, поэтому существует соответствующий международный стандарт, которыйGeoJson
, который описывает географическую информацию с помощью точек, линий и полигонов.
Указав тип точки, линии и многоугольника, а затем указав несколько позиций координат, можно описать соответствующую форму.
Доступ к данным Geojson можно получить черезgeojson.ioПредварительный просмотр этого сайта.
Например, геоджсон карты Китая:
С этим json просто используйте Three.js, чтобы нарисовать его через линии и полигоны.
Но есть еще один вопрос.Информацию о широте и долготе, записанную в geojson, как преобразовать ее в двухмерные координаты, чтобы отрисовать?
Это включает в себя墨卡托转换
, который должен преобразовать широту и долготу в двумерные координаты.
Это преобразование не нужно выполнять самостоятельно, и его можно выполнить с помощью встроенной функции преобразования координат Меркатора d3.
Таким образом, мы используем Three.js для рисования карты на основе geojson.
Мы также хотим нарисовать кривую из Пекина в Шанхай.Это делается с помощью кривой Безье.Мы знаем координаты двух конечных точек, а контрольная точка находится посередине.
Так как же узнать координаты двух конечных точек, то есть Шанхая и Пекина?
Вы можете использовать инструмент «Система захвата координат Baidu», чтобы щелкнуть определенное место на карте, и вы можете напрямую получить широту и долготу этого положения. Затем мы делаем преобразование Меркатора, чтобы получить координаты.
Нарисована карта и построена кривая движения, затем отрегулируйте положение камеры и медленно двигайтесь из Пекина в Шанхай.
Идея ясна, давайте напишем код.
Код
Мы собираемся импортировать d3, а затем использовать функцию преобразования Меркатора d3,
const projection = d3.geoMercator()
.center([116.412318,39.909843])
.translate([0, 0]);
Координаты средней точки — это широта и долгота Пекина, которые мы получили с помощью «Инструмента выбора координат Baidu».
Координаты Пекина и Шанхая также можно получить, переведя широту и долготу в меркаторские:
let beijingPosition= projection([116.412318,39.909843]);
let shanghaiPosition = projection([121.495721,31.236797]);
Не спешите рисовать кривую путешествия, давайте сначала нарисуем карту.
Сначала загрузите geojson:
const loader = new THREE.FileLoader();
loader.load('./data/china.json', (data) => {
const jsondata = JSON.parse(data);
generateGeometry(jsondata);
})
Затем нарисуйте карту на основе информации JSON.
Пройдите данные geojson, преобразуйте каждую широту и долготу в координаты через Меркатор, а затем нарисуйте их линиями и полигонами соответственно.
При рисовании полигонов я использую желтый цвет для Пекина и Шанхая и синий цвет для остальных городов.
function generateGeometry(jsondata) {
const map = new THREE.Group();
jsondata.features.forEach((elem) => {
const province = new THREE.Group();
// 经纬度信息
const coordinates = elem.geometry.coordinates;
coordinates.forEach((multiPolygon) => {
multiPolygon.forEach((polygon) => {
// 画轮廓线
const line = drawBoundary(polygon);
// 画多边形
const provinceColor = ['北京市', '上海市'].includes(elem.properties.name) ? 'yellow' : 'blue';
const mesh = drawExtrudeMesh(polygon, provinceColor);
province.add(line);
province.add(mesh);
});
});
map.add(province);
})
scene.add(map);
}
Затем нарисуйте контурные линии и нарисуйте многоугольники соответственно:
Контур (Line) заключается в том, чтобы указать ряд вершин для формирования геометрии (Geometry), а затем указать цвет материала (Material) как желтый:
function drawBoundary(polygon) {
const lineGeometry = new THREE.Geometry();
for (let i = 0; i < polygon.length; i++) {
const [x, y] = projection(polygon[i]);
lineGeometry.vertices.push(new THREE.Vector3(x, -y, 0));
}
const lineMaterial = new THREE.LineBasicMaterial({
color: 'yellow'
});
return new THREE.Line(lineGeometry, lineMaterial);
}
Эффект сейчас такой:
Полигоны — это ExtrudeGeometry, то есть можно сначала нарисовать фигуру, а потом сделать ее трехмерной, растянув.
function drawExtrudeMesh(polygon, color) {
const shape = new THREE.Shape();
for (let i = 0; i < polygon.length; i++) {
const [x, y] = projection(polygon[i]);
if (i === 0) {
shape.moveTo(x, -y);
}
shape.lineTo(x, -y);
}
const geometry = new THREE.ExtrudeGeometry(shape, {
depth: 0,
bevelEnabled: false
});
const material = new THREE.MeshBasicMaterial({
color,
transparent: true,
opacity: 0.2,
})
return new THREE.Mesh(geometry, material);
}
Используйте moveTo для первой точки и lineTo для следующих точек, чтобы сформировать многоугольник, а затем укажите толщину 0 и укажите, что нет необходимости в дополнительном скосе на стороне.
Таким образом, мы закрасили каждую провинцию цветом: Пекин и Шанхай — желтым, а остальные — синим.
Затем нарисуйте кривую Безье между Пекином и Шанхаем:
const line = drawLine(beijingPosition, shanghaiPosition);
scene.add(line);
Кривые Безье рисуются с помощью QuadraticBezierCurve3, а контрольная точка указывает точку посередине.
function drawLine(pos1, pos2) {
const [x0, y0, z0] = [...pos1, 0];
const [x1, y1, z1] = [...pos2, 0];
const geomentry = new THREE.Geometry();
geomentry.vertices = new THREE.QuadraticBezierCurve3(
new THREE.Vector3(-x0, -y0, z0),
new THREE.Vector3(-(x0 + x1) / 2, -(y0 + y1) / 2, -10),
new THREE.Vector3(-x1, -y1, z1),
).getPoints();
const material = new THREE.LineBasicMaterial({color: 'white'});
const line = new THREE.Line(geomentry, material);
line.rotation.y = Math.PI;
return line;
}
Таким образом рисуется карта и дорожка путешествия:
Конечно, есть также коды инициализации для рендереров, камер и источников света:
Рендерер:
const renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0x000000);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
Средство визуализации устанавливает черный цвет фона и размер холста в соответствии с размером окна.
свет:
let ambientLight = new THREE.AmbientLight(0xffffff);
scene.add(ambientLight);
Освещение использует окружающий свет, что означает, что свет и тень одинаковы во всех направлениях.
камера:
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 0, 10);
camera.lookAt(scene.position);
Камера использует перспективную камеру, которая характеризуется большим ближним и дальним малым, и для нее необходимо указать четыре параметра, такие как угол обзора, соотношение сторон и диапазон дальнего и ближнего.
Позиция установлена на 0 0 10. Обратите внимание на 0 0 0 в этой позиции, которая является видом сверху над Пекином (мы указали Пекин в качестве центра при преобразовании Меркатора).
После изменения положения камеры карта, которую вы видите, стала намного больше:
Следующим шагом будет рендеринг кадр за кадром и перемещение положения камеры при рендеринге каждого кадра Это эффект перемещения из Пекина в Шанхай:
function render() {
if(camera.position.x < shanghaiPosition[0]) {
camera.position.x += 0.1;
}
if(camera.position.y > -shanghaiPosition[1]) {
camera.position.y -= 0.2;
}
renderer.render(scene, camera);
requestAnimationFrame(render);
}
Готово! Давайте посмотрим на окончательный эффект:
Код загружен на GitHub:GitHub.com/кварк глюон P…
Также размещаю копию здесь:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>map-travel</title>
<style>
html body {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<script src="./js/three.js"></script>
<script src="./js/d3.js"></script>
<script>
const scene = new THREE.Scene();
const renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0x000000);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 0, 10);
camera.lookAt(scene.position);
let ambientLight = new THREE.AmbientLight(0xffffff);
scene.add(ambientLight);
function create() {
const loader = new THREE.FileLoader();
loader.load('./data/china.json', (data) => {
const jsondata = JSON.parse(data);
generateGeometry(jsondata);
})
}
const projection = d3.geoMercator()
.center([116.412318,39.909843])
.translate([0, 0]);
let beijingPosition= projection([116.412318,39.909843]);
let shanghaiPosition = projection([121.495721,31.236797]);
function drawBoundary(polygon) {
const lineGeometry = new THREE.Geometry();
for (let i = 0; i < polygon.length; i++) {
const [x, y] = projection(polygon[i]);
lineGeometry.vertices.push(new THREE.Vector3(x, -y, 0));
}
const lineMaterial = new THREE.LineBasicMaterial({
color: 'yellow'
});
return new THREE.Line(lineGeometry, lineMaterial);
}
function drawExtrudeMesh(polygon, color) {
const shape = new THREE.Shape();
for (let i = 0; i < polygon.length; i++) {
const [x, y] = projection(polygon[i]);
if (i === 0) {
shape.moveTo(x, -y);
}
shape.lineTo(x, -y);
}
const geometry = new THREE.ExtrudeGeometry(shape, {
depth: 0,
bevelEnabled: false
});
const material = new THREE.MeshBasicMaterial({
color,
transparent: true,
opacity: 0.2,
})
return new THREE.Mesh(geometry, material);
}
function generateGeometry(jsondata) {
const map = new THREE.Group();
jsondata.features.forEach((elem) => {
const province = new THREE.Group();
const coordinates = elem.geometry.coordinates;
coordinates.forEach((multiPolygon) => {
multiPolygon.forEach((polygon) => {
const line = drawBoundary(polygon);
const provinceColor = ['北京市', '上海市'].includes(elem.properties.name) ? 'yellow' : 'blue';
const mesh = drawExtrudeMesh(polygon, provinceColor);
province.add(line);
province.add(mesh);
});
});
map.add(province);
})
scene.add(map);
const line = drawLine(beijingPosition, shanghaiPosition);
scene.add(line);
}
function render() {
if(camera.position.x < shanghaiPosition[0]) {
camera.position.x += 0.1;
}
if(camera.position.y > -shanghaiPosition[1]) {
camera.position.y -= 0.2;
}
renderer.render(scene, camera);
requestAnimationFrame(render);
}
function drawLine(pos1, pos2) {
const [x0, y0, z0] = [...pos1, 0];
const [x1, y1, z1] = [...pos2, 0];
const geomentry = new THREE.Geometry();
geomentry.vertices = new THREE.QuadraticBezierCurve3(
new THREE.Vector3(-x0, -y0, z0),
new THREE.Vector3(-(x0 + x1) / 2, -(y0 + y1) / 2, -10),
new THREE.Vector3(-x1, -y1, z1),
).getPoints();
const material = new THREE.LineBasicMaterial({color: 'white'});
const line = new THREE.Line(geomentry, material);
line.rotation.y = Math.PI;
return line;
}
create();
render();
</script>
</body>
</html>
Суммировать
Представление формы карты основано на спецификации geojson, состоящей из точек, линий, полигонов и другой информации.
Чтобы нарисовать карту с помощью Three.js или других методов рисования, вам нужно только загрузить данные geojson, а затем нарисовать каждую часть с помощью линий и полигонов.
Перед рисованием необходимо преобразовать широту и долготу в координаты, что требует преобразования Меркатора.
Мы рисуем линии с помощью Three.js, указывая ряд вершин для формирования геометрии, а рисование полигонов — это рисование формы, а затем ее экструдирование в 3D с помощью ExtrudeGeometry. Преобразования Меркатора напрямую используют встроенные функции d3. Эффект путешествия достигается за счет покадрового перемещения положения камеры.
Знаком с преобразованиями geojson и mercator, даже если это визуализация начального уровня, связанная с географией.
Вы также хотите сделать некоторые визуализации или взаимодействия, связанные с географией? Приходите и попробуйте.