D3 vs Scipy(Voronoi 图实现)

D3 vs Scipy (Voronoi diagram implementation)

背景

我正在处理 csv 文件中包含的一组 8000 个地理点。一方面,我创建了使用这些点构建的 Voronoi 图的可视化 - 它是使用 D3 库完成的。另一方面,我使用 Scipy.

在 Python 中计算这些 Voronoi 图

我的工作逻辑很简单——我在 Python 那边处理我的数据,制作热图、分析等等,然后我使用 D3 可视化效果。但是今天无意间发现Scipy和D3做的Voronoi图不一样。我注意到在使用 geojson.io 绘制在 Python 中制作的 Voronois 的 GeoJsons 之后,我只是想看看我是否可以将那里的所有内容可视化。

正如我所说,Voronois 是不同的 - 其中一些有不同的角度,有些甚至有额外的顶点。

问题:

为什么会这样?为什么这两个库(D3 和 Scipy)计算出的 Voronoi 图不同?

进一步说明

如何在 D3 端完成:基于 Chris Zetter 示例 http://chriszetter.com/blog/2014/06/15/building-a-voronoi-map-with-d3-and-leaflet/ 我将纬度和经度转换为自定义投影以在 mapbox 地图上将其可视化。

var voronoi = d3.geom.voronoi()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.clipExtent([[N_W.x , N_W.y],[S_E.x, S_E.y]])

我根据地图边界内可见的点 + 一些填充 (filteredPoints) 创建 Voronoi

  filteredPoints = points.filter(function(d) {
  var latlng = new L.LatLng(d.latitude, d.longitude);
  if (!drawLimit.contains(latlng)) { return false };
  // this translates points from coordinates to pixels
  var point = map.latLngToLayerPoint(latlng);
  key = point.toString();
  if (existing.has(key)) { return false };
  existing.add(key);
  d.x = point.x;
  d.y = point.y;
  return true;
  });

voronoi(filteredPoints).forEach(function(d) { d.point.cell = d});

Python这边是怎么做的:我用scipy.spatial.Voronoi.

from scipy.spatial import Voronoi

def create_voronois():
    points = numpy.array(points_list)
    vor = Voronoi(points)

其中 "points_list" 是我的 8000 个地理点的列表。

编辑:

我的可视化屏幕截图 - 黑色边框是用 D3 制作的 Voronois,白色边框是由 scipy.spatial.Voronoi 制作的。我们可以看到 scipy 是错误的。有人比较过这两个库吗?

http://imgur.com/b1ndx0F

编码为运行。它使用计算错误的 Voronois 打印 GeoJson。

import numpy
from scipy.spatial import Voronoi
from geojson import FeatureCollection, Feature, Polygon

points = [
[22.7433333333000,  53.4869444444000],
[23.2530555556000,  53.5683333333000],
[23.1066666667000,  53.7200000000000],
[22.8452777778000,  53.7758333333000],
[23.0952777778000,  53.4413888889000],
[23.4152777778000,  53.5233333333000],
[22.9175000000000,  53.5322222222000],
[22.7197222222000   ,53.7322222222000],
[22.9586111111000,  53.4594444444000],
[23.3425000000000,  53.6541666667000],
[23.0900000000000,  53.5777777778000],
[23.2283333333000,  53.4713888889000],
[23.3488888889000,  53.5072222222000],
[23.3647222222000   ,53.6447222222000]]

def create_voronois(points_list):
    points = numpy.array(points_list)
    vor = Voronoi(points)
    point_voronoi_list = []
    feature_list = []
    for region in range(len(vor.regions) - 1):
        vertice_list = []
        for x in vor.regions[region]:
            vertice = vor.vertices[x]
            vertice = (vertice[1], vertice[0])
            vertice_list.append(vertice)
            polygon = Polygon([vertice_list])
            feature = Feature(geometry=polygon, properties={})
            feature_list.append(feature)

    feature_collection = FeatureCollection(feature_list)
    print feature_collection

create_voronois(points)

显然,您的 javascript 代码在计算 Voronoi 图之前对数据应用了转换。此转换不会保留点的相对距离,因此不会生成与您的 scipy 代码相同的结果。请注意,我并不是说您的 d3 版本不正确。鉴于数据是纬度和经度,您在 javascript 代码中所做的可能是正确的。但是要将其与 scipy 代码进行比较,如果您希望获得相同的 Voronoi 图,则必须进行相同的转换。

下面的脚本显示,如果您保留输入点的相对距离,scipy 的 Voronoi 函数和 d3.geom.voronoi 生成相同的图表。

这是一个使用 scipy 的 Voronoi 代码的脚本:

import numpy
from scipy.spatial import Voronoi, voronoi_plot_2d
import matplotlib.pyplot as plt


points = [
[22.7433333333000,  53.4869444444000],
[23.2530555556000,  53.5683333333000],
[23.1066666667000,  53.7200000000000],
[22.8452777778000,  53.7758333333000],
[23.0952777778000,  53.4413888889000],
[23.4152777778000,  53.5233333333000],
[22.9175000000000,  53.5322222222000],
[22.7197222222000,  53.7322222222000],
[22.9586111111000,  53.4594444444000],
[23.3425000000000,  53.6541666667000],
[23.0900000000000,  53.5777777778000],
[23.2283333333000,  53.4713888889000],
[23.3488888889000,  53.5072222222000],
[23.3647222222000,  53.6447222222000]]


vor = Voronoi(points)

voronoi_plot_2d(vor)
plt.axis('equal')
plt.xlim(22.65, 23.50)
plt.ylim(53.35, 53.85)
plt.show()

它生成这个图:

现在这里有一个 javascript 程序,它使用 d3.geom.voronoi:

<html>
<head>
    <script type="text/javascript" src="http://mbostock.github.com/d3/d3.js"></script>
    <script type="text/javascript" src="http://mbostock.github.com/d3/d3.geom.js"></script>
</head>
<body>
  <div id="chart">
  </div>
  <script type="text/javascript">

  // This code is a hacked up version of http://bl.ocks.org/njvack/1405439

  var w = 800,
      h = 400;

  var data = [
    [22.7433333333000,  53.4869444444000],
    [23.2530555556000,  53.5683333333000],
    [23.1066666667000,  53.7200000000000],
    [22.8452777778000,  53.7758333333000],
    [23.0952777778000,  53.4413888889000],
    [23.4152777778000,  53.5233333333000],
    [22.9175000000000,  53.5322222222000],
    [22.7197222222000,  53.7322222222000],
    [22.9586111111000,  53.4594444444000],
    [23.3425000000000,  53.6541666667000],
    [23.0900000000000,  53.5777777778000],
    [23.2283333333000,  53.4713888889000],
    [23.3488888889000,  53.5072222222000],
    [23.3647222222000,  53.6447222222000]
  ];

  // Translate and scale the points.  The same scaling factor (2*h) must be used
  // on x and y to preserve the relative distances among the points.
  // The y coordinates are also flipped.
  var vertices = data.map(function(point) {return [2*h*(point[0]-22.5), h - 2*h*(point[1]-53.4)]})

  var svg = d3.select("#chart")
    .append("svg:svg")
      .attr("width", w)
      .attr("height", h);
  var paths, points;

  points = svg.append("svg:g").attr("id", "points");
  paths = svg.append("svg:g").attr("id", "point-paths");

  paths.selectAll("path")
      .data(d3.geom.voronoi(vertices))
    .enter().append("svg:path")
      .attr("d", function(d) { return "M" + d.join(",") + "Z"; })
      .attr("id", function(d,i) { 
        return "path-"+i; })
      .attr("clip-path", function(d,i) { return "url(#clip-"+i+")"; })
      .style("fill", d3.rgb(230, 230, 230))
      .style('fill-opacity', 0.4)
      .style("stroke", d3.rgb(50,50,50));

  points.selectAll("circle")
      .data(vertices)
    .enter().append("svg:circle")
      .attr("id", function(d, i) { 
        return "point-"+i; })
      .attr("transform", function(d) { return "translate(" + d + ")"; })
      .attr("r", 2)
      .attr('stroke', d3.rgb(0, 50, 200));

  </script>
</body>
</html>

它生成:

根据对结果的目视检查,我认为它们生成了相同的 Voronoi 图。