原文:http://www.smartjava.org/content/render-geographic-information-3d-threejs-and-d3js

The last couple of days I've been playing around with three.js and geo information. I wanted to be able to render map/geo data (e.g. in geojson format) inside the three.js scene. That way I have another dimension I could use to show a specific metric instead of just using the color in a 2D map. In this article I'll show you how you can do this. The example we'll create shows a 3D map of the Netherlands, rendered in Three.js, that uses a color to indicate the population density per municipality and the height of each municipality represents the actual number of residents.

Or if you can look at aworking example.

This information is based on open data available from the Dutch government. If you look at the source from the example, you can see the json we use for this. For more information on geojson and how to parse it see the other articles I did on this subject:

To get this working we'll take the following steps:

  1. Load the input geo data
  2. Setup a three.js scene
  3. Convert the input data to a Three.js path using d3.js
  4. Set the color and height of the Three.js object
  5. Render everything

Just a reminder to see everything working, just look at theexample.

Load the input geo data

D3.js has support to load json and directly transform it to an SVG path. Though this is a convenient way, I only needed the path data, not the complete SVG elements. So to load json I just used jquery's json support.

// get the data
jQuery.getJSON('data/cities.json', function(data, textStatus, jqXHR) {
..
});

This will load the data and pass it in the data object to the supplied function.

Setup a three.js scene

Before we do anything with the data lets first setup a basic Three.js scene.

// Set up the three.js scene. This is the most basic setup without
// any special stuff
function initScene() {
// set the scene size
var WIDTH = 600, HEIGHT = 600; // set some camera attributes
var VIEW_ANGLE = 45, ASPECT = WIDTH / HEIGHT, NEAR = 0.1, FAR = 10000; // create a WebGL renderer, camera, and a scene
renderer = new THREE.WebGLRenderer({antialias:true});
camera = new THREE.PerspectiveCamera(VIEW_ANGLE, ASPECT,
NEAR, FAR);
scene = new THREE.Scene(); // add and position the camera at a fixed position
scene.add(camera);
camera.position.z = 550;
camera.position.x = 0;
camera.position.y = 550;
camera.lookAt( scene.position ); // start the renderer, and black background
renderer.setSize(WIDTH, HEIGHT);
renderer.setClearColor(0x000); // add the render target to the page
$("#chart").append(renderer.domElement); // add a light at a specific position
var pointLight = new THREE.PointLight(0xFFFFFF);
scene.add(pointLight);
pointLight.position.x = 800;
pointLight.position.y = 800;
pointLight.position.z = 800; // add a base plane on which we'll render our map
var planeGeo = new THREE.PlaneGeometry(10000, 10000, 10, 10);
var planeMat = new THREE.MeshLambertMaterial({color: 0x666699});
var plane = new THREE.Mesh(planeGeo, planeMat); // rotate it to correct position
plane.rotation.x = -Math.PI/2;
scene.add(plane);
}

Nothing to special, the comments inline should nicely explain what we're doing here. Next it gets more interesting.

Convert the input data to a Three.js path using d3.js

What we need to do next is convert our geojson input format to a THREE.Path that we can use in our scene. Three.js itself doesn't support geojson or SVG for that matter. Luckily though someone already started work on integrating d3.js with three.js. This project is called "d3-threeD" (sources can be found on github here ). With this extension you can automagically render SVG elements in 3D directly from D3.js. Cool stuff, but it didn't allow me any control over how the elements were rendered. It does however contain a function we can use for our scenario. If you look through the source code of this project you'll find a method called "transformSVGPath". This method converts an SVG path string to a Three.Shape element. Unfortunately this method isn't exposed, but that's quickly solved by adding this to the d3-threeD.js file:

// at the top
var transformSVGPathExposed;
...
// within the d3threeD(exports) function
transformSVGPathExposed = transformSVGPath;
</javscript> This way we can call this method separately. Now that we have a way to transform an SVG path to a Three.js shape, we only need to convert the geojson to an SVG string and pass it to this function. We can use the geo functionaly from D3.js for this: <javascript>
geons.geoConfig = function() {
this.TRANSLATE_0 = appConstants.TRANSLATE_0;
this.TRANSLATE_1 = appConstants.TRANSLATE_1;
this.SCALE = appConstants.SCALE; this.mercator = d3.geo.mercator();
this.path = d3.geo.path().projection(this.mercator); this.setupGeo = function() {
var translate = this.mercator.translate();
translate[0] = this.TRANSLATE_0;
translate[1] = this.TRANSLATE_1; this.mercator.translate(translate);
this.mercator.scale(this.SCALE);
}
}

The path variable from the previous piece of code can now be used like this:

var feature = geo.path(geoFeature);

To convert a geojson element to an SVG path. So how does this look combined?

// add the loaded gis object (in geojson format) to the map
function addGeoObject() {
// keep track of rendered objects
var meshes = [];
... // convert to mesh and calculate values
for (var i = 0 ; i < data.features.length ; i++) {
var geoFeature = data.features[i]
var feature = geo.path(geoFeature);
// we only need to convert it to a three.js path
var mesh = transformSVGPathExposed(feature);
// add to array
meshes.push(mesh); ...
}

As you can see we iterate over the data.features list (this contains all the geojson representations of the municipalities). Each municipality is converted to an svg string, and each svg string is converted to a mesh. This mesh is a Three.js object that we can render on the scene.

Set the color and height of the Three.js object

Now we just need to set the height and the color of the Three.js shape and add it to the scene. The extended addGeoObject method now looks like this:

// add the loaded gis object (in geojson format) to the map
function addGeoObject() {
// keep track of rendered objects
var meshes = [];
var averageValues = [];
var totalValues = []; // keep track of min and max, used to color the objects
var maxValueAverage = 0;
var minValueAverage = -1; // keep track of max and min of total value
var maxValueTotal = 0;
var minValueTotal = -1; // convert to mesh and calculate values
for (var i = 0 ; i < data.features.length ; i++) {
var geoFeature = data.features[i]
var feature = geo.path(geoFeature);
// we only need to convert it to a three.js path
var mesh = transformSVGPathExposed(feature);
// add to array
meshes.push(mesh); // we get a property from the json object and use it
// to determine the color later on
var value = parseInt(geoFeature.properties.bev_dichth);
if (value > maxValueAverage) maxValueAverage = value;
if (value < minValueAverage || minValueAverage == -1) minValueAverage = value;
averageValues.push(value); // and we get the max values to determine height later on.
value = parseInt(geoFeature.properties.aant_inw);
if (value > maxValueTotal) maxValueTotal = value;
if (value < minValueTotal || minValueTotal == -1) minValueTotal = value; totalValues.push(value);
} // we've got our paths now extrude them to a height and add a color
for (var i = 0 ; i < averageValues.length ; i++) { // create material color based on average
var scale = ((averageValues[i] - minValueAverage) / (maxValueAverage - minValueAverage)) * 255;
var mathColor = gradient(Math.round(scale),255);
var material = new THREE.MeshLambertMaterial({
color: mathColor
}); // create extrude based on total
var extrude = ((totalValues[i] - minValueTotal) / (maxValueTotal - minValueTotal)) * 100;
var shape3d = meshes[i].extrude({amount: Math.round(extrude), bevelEnabled: false}); // create a mesh based on material and extruded shape
var toAdd = new THREE.Mesh(shape3d, material); // rotate and position the elements nicely in the center
toAdd.rotation.x = Math.PI/2;
toAdd.translateX(-490);
toAdd.translateZ(50);
toAdd.translateY(extrude/2); // add to scene
scene.add(toAdd);
}
} // simple gradient function
function gradient(length, maxLength) { var i = (length * 255 / maxLength);
var r = i;
var g = 255-(i);
var b = 0; var rgb = b | (g << 8) | (r << 16);
return rgb;
}

A big piece of code, but not that complex. What we do here is we keep track of two values for each municipality: the population density and the total population. These values are used to respectively calculate the color (using the gradient function) and the height. The height is used in the Three.js extrude function which converts our 2D Three.Js path to a 3D shape. The color is used to define a material. This shape and material is used to create the Mesh that we add to the scene.

Render everything

All that is left is to render everything. For this example we're not interested in animations or anything so we can make a single call to the renderer:

renderer.render( scene, camera );

And the result is as you saw in the beginning. The following image shows a different example. This time we once again show the population density, but now the height represents the land area of the municipality.

I'm currently creating a new set of geojson data, but this time for the whole of Europe. So in the next couple of weeks expect some articles using maps of Europe.

svg拉伸,原来凹凸可以这么玩的更多相关文章

  1. 【带着canvas去流浪(11)】Three.js入门学习笔记

    目录 一. 资料推荐及建议 二. Three.js中的基本概念 三.重点笔记 四.补充示例 示例代码托管在:http://www.github.com/dashnowords/blogs 博客园地址: ...

  2. 多种css3时尚侧栏菜单展开显示效果Off-Canvas Menu Effects

    今天我们想分享多种css3时尚侧栏菜单展开显示效果.侧边栏应用广泛,我们之前已经产生了一些效果灵感.风格演变,今天我们要展示一套新的灵感的现代效果.不同的布局和菜单的同步转换和页面可以让一切看起来更有 ...

  3. 线条之美,玩转SVG线条动画

    线条之美,玩转SVG线条动画 作者:AlloyTeam www.alloyteam.com/2017/02/the-beauty-of-the-lines-break-lines-svg-animat ...

  4. selenium玩转svg操作

    今天写脚本发现页面有svg结构,里面的元素无法定位,查找很多资料,然后就记录下来 初步尝试直接在页面中获取svg中包含元素的xpath,直接利用selenium方法访问,无法捕获到相关元素信息. SV ...

  5. 玩转SVG线条动画

    在上一节的<SVG线条动画实现原理>一文中,了解了SVG中线动画是怎么做的.在这篇文章中,了解了怎么借助Sketch这样的制作软件绘制SVG的路径,然后借助于SVG的stroke-dash ...

  6. o'Reill的SVG精髓(第二版)学习笔记——第十一章

    第十一章:滤镜 11.1滤镜的工作原理 当SVG阅读器程序处理一个图形对象时,它会将对象呈现在位图输出设备上:在某一时刻,阅读器程序会把对象的描述信息转换为一组对应的像素,然后呈现在输出设备上.例如我 ...

  7. 深度掌握SVG路径path的贝塞尔曲线指令

    一.数字.公式.函数.变量,哦,NO! 又又一次说起贝塞尔曲线(英语:Bézier curve,维基百科详尽中文释义戳这里),我最近在尝试实现复杂的矢量图形动画,发现对贝塞尔曲线的理解馒头那么厚,是完 ...

  8. 理解SVG的viewport,viewBox,preserveAspectRatio

    万丈高楼平地起,基础很重要. viewport 表示SVG可见区域的大小,或者可以想象成舞台大小,画布大小. <svg width="500" height="30 ...

  9. svg gradient

    SVG和canvas中是一样的,都是使用标准的HTML/CSS中的颜色表示方法,这些颜色都可以用于fill和stroke属性. 基本有下面这些定义颜色的方式: 1. 颜色名字: 直接使用颜色名字red ...

随机推荐

  1. mybatis 中的where标签

    mybatis中的where标签可以去除 开头的 and 或者 or 但是放在后面的不行 失败的: <select id="countNotesByParam" parame ...

  2. 如何在MAC OS X下安装配置java开发工具

    简介: Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承.指针等概念,因此Java语言具有功能强大和简单易用两个特征.Java语言作为静态面向对象编程语 ...

  3. phpstorm10.0.3破解版安装教程及汉化方法

    phpstorm是一个轻量级且便捷的PHP IDE,其旨在提供用户效率,可深刻理解用户的编码,提供智能代码补全,快速导航以及即时错误检查.不但是php开发的利器,前端开发也是毫不逊色的.下面记录php ...

  4. JavaScript闭包理解【关键字:普通函数、闭包、解决获取元素标签索引】

    以前总觉得闭包很抽象,很难理解,所以百度一下"闭包"概览,百度的解释是:“闭包是指可以包含自由(未绑定到特定对象)变量的代码块:这些变量不是在这个代码块内或者任何全局上下文中定义的 ...

  5. (有趣)chrome不同浏览器版本对display:flex和溢出隐藏显示省略符号的bug

    项目中碰到一个十分有趣的情形: 布局要求是这样:右边创建新订单是固定宽度80px,左侧是自适应宽度,溢出隐藏.如下图. 这里布局不用说肯定使用display:flex的.左侧flex:1;右侧widt ...

  6. Objective-C 数据集合

    1.NSSet 1.是一个无序的,管理多个对象的集合类,最大特点 是集合中不允许出现重复对象,和数学上的集合含义是一 样的 2.除了无序.不许重复之外,其它功能和NSArray是一样的 2.NSArr ...

  7. iOS之9.3真机适配-Could not find Developer Disk Image问题

    Could not find Developer Disk Image 这是由于真机系统过高或者过低,Xcode中没有匹配的配置包文件,我们可以通过这个路径进入配置包的存放目录: /Applicati ...

  8. iOS之APP应用图标的设置

    图标是IOS程序包所必需的组成部分.如果你没有提供程序所需的各种尺寸的图标,程序上传发布时可能会无法通过验证.IOS程序为兼顾不同的应用场景,定义了多个不同规格的图标,并以不同的命名区分: IOS图标 ...

  9. MVC学习系列5--Layout布局页和RenderSection的使用

    我们开发网站项目的时候,都会遇到这样的问题:就是页面怎么统一风格,有一致的外观,在之前ASP.NET的时代,我们有两种选择,一个是使用MasterPage页,一个是手动,自己在每个页面写CSS样式,但 ...

  10. IT从业者的职业道路(从程序员到部门经理) - 项目管理系列文章

    十年前,笔者还是一个刚毕业的大学生,对IT业只是停留在学校的编程知识领域.刚出社会,有很多需要学习的地方.在这十年间,笔者经历了程序员,技术经理,项目经理,部门经理等职位.本文就是要说说如何从程序员到 ...