svg拉伸,原来凹凸可以这么玩

原文: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.

时间: 2024-08-25 04:46:34

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

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

线条之美,玩转SVG线条动画 作者:AlloyTeam www.alloyteam.com/2017/02/the-beauty-of-the-lines-break-lines-svg-animation/ 如有好文章投稿,请点击 → 这里了解详情 通常来说web前端实现动画效果主要通过下面几种方案: css动画:利用css3的样式效果可以将dom元素做出动画的效果来. canvas动画:利用canvas提供的API,然后利用清除-渲染这样一帧一帧的做出动画效果. svg动画:同样svg也提供

selenium玩转svg操作

今天写脚本发现页面有svg结构,里面的元素无法定位,查找很多资料,然后就记录下来 初步尝试直接在页面中获取svg中包含元素的xpath,直接利用selenium方法访问,无法捕获到相关元素信息. SVG包含一些图形元素,比如line,rect,circle等,很多情况下我们可以点击SVG上的元素触发一些event,比如打开context menu.在一个pie里选择一个portion等.但是SVG在html看来是一个单独的元素,我们怎么点击svg里的元素呢?下面记录下 Firefox和Chrom

o&#39;Reill的SVG精髓(第二版)学习笔记——第十一章

第十一章:滤镜 11.1滤镜的工作原理 当SVG阅读器程序处理一个图形对象时,它会将对象呈现在位图输出设备上:在某一时刻,阅读器程序会把对象的描述信息转换为一组对应的像素,然后呈现在输出设备上.例如我们用SVG的<filter>元素指定一组操作(也称作基元,primitive),在对象的旁边显示一个模糊的投影,然后把这个滤镜附加给一个对象: <fliter id="drop-shadow"> <!-- 这是滤镜操作 --> </fliter&g

超级强大的SVG SMIL animation动画详解

本文花费精力惊人,具有先驱前瞻性,转载规则以及申明见文末,当心予以追究.本文地址:http://www.zhangxinxu.com/wordpress/?p=4333 //zxx: 本文的SVG在有缓存时候是无动画效果,此时您可以试着[右键-新标签页打开图片]. 一.SVG SMIL animation概览 1. SMIL是什么?SMIL不是指「水蜜梨」,而是Synchronized Multimedia Integration Language(同步多媒体集成语言)的首字母缩写简称,是有标准

SVG Use(转)

转自:http://www.zhangxinxu.com/wordpress/2014/07/introduce-svg-sprite-technology/ 未来必热:SVG Sprite技术介绍 一.Sprite技术 这里所说的Sprite技术,没错,类似于CSS中的Sprite技术.图标图形整合在一起,实际呈现的时候准确显示特定图标. 二.SVG Sprite与symbol元素 目前,SVG Sprite最佳实践是使用symbol元素.symbol元素是什么呢?单纯翻译的话,是"符号&qu

【转】Android应用开发之PNG、IconFont、SVG图标资源优化详解

1 背景 最近因为一些个人私事导致好久没写博客了,多事之年总算要过去了,突然没了动力,所以赶紧先拿个最近项目中重构的一个小知识点充下数,老题重谈. 在我们App开发中大家可能都会有过如下痛疾(程序员和设计妹妹注意喽): 好多小的图标好烦人,又占体积还要考虑分辨率,一拉伸就模糊等. 同一个图标不同状态还有不同颜色的多张. 总是幻想IOS.Android.Web等对于一个图标只切一次图多好. 如果你有过类似的痛疾那么下面讨论的故事就是一个完美的解决方案,当然了,采用下面方案对于重型应用或者固件级的优

Android APK瘦身大法——SVG图片瘦身

前两天和上家公司的上司无意聊了聊工作的事,也就顺便扯到了apk瘦身上.主要是通过SVG进行图片压缩来减少app的大小.下面我就详细介绍一下如何实现SVG的图片压缩. SVG的优点 SVG 可被非常多的工具读取和修改(比如记事本),由于使用xml格式定义,所以可以直接被当作文本文件打开,看里面的数据: SVG 与 JPEG 和 GIF 图像比起来,尺寸更小,且可压缩性更强,SVG 图就相当于保存了关键的数据点,比如要显示一个圆,需要知道圆心和半径,那么SVG 就只保存圆心坐标和半径数据,而平常我们

玩转HTML5移动页面

(1) 动画雪碧图 涉及的动画十分多,用的元素也十分多,请务必使用雪碧图(Sprite)! 网上的工具有一些可以帮助你生成雪碧图的工具,例如CssGaga,GoPng等等,自动化构建工具Grunt和Gulp也提供了相应插件. 特别地,如果单张雪碧图面积实在太大,可以拆分雪碧图,例如拆分成2-4张,因为现代浏览器都支持4-6个同源请求下载,若资源实在太多,也可以考虑把静态资源放在不同源域名下去请求,这里牺牲多几个请求换来图片同时加载比一张图片慢慢加载要好,当然,这需要具体情况去衡量. 顺便提一下,

理解SVG的viewport,viewBox,preserveAspectRatio

万丈高楼平地起,基础很重要. viewport 表示SVG可见区域的大小,或者可以想象成舞台大小,画布大小. <svg width="500" height="300"></svg> 上面的SVG代码定义了一个视区,宽500单位,高300单位. 注意这里的措辞是"单位",不是"像素".虽然说,width/height如果是纯数字,使用的就是"像素"作为单位的. 也就是说,上面SVG的