六边形未显示在地理图块地图D3.JS

时间:2019-02-11 14:30:16

标签: d3.js hexagonal-tiles

我有一张使用d3瓷砖创建的地图。我添加了代码以显示基于经度和纬度从数据集中收集的六边形。但是六边形没有显示出来。我只发现了一些示例,这些示例在hexbin中绘制普通的笛卡尔数据,而不是在d3tile制成的地图上放置hexbin的经度。为什么我的六边形框不显示?

这是定义投影,图块和六边形框的方式:

var projection = d3.geo.mercator()
   .scale((1 << 22) / 2 / Math.PI) 
   .translate([width / 2, height / 2]);

var tile = d3.geo.tile()
  .size([width, height]);

// define hexbins
var hexbin = d3.hexbin()
   .size([width, height])
   .radius(4);

这是我处理数据并将hexbins添加到地图的方式:

data.forEach(function (d) {
    d.lat = +d.lat;
    d.lon = +d.lon;
});

points = [];

// x,y maps to lng,lat - ref[2]
data.forEach(function (d) {
   d.lat = +d.lat;
   d.lon = +d.lon;
   var x = projection([d.lon, d.lat])[0];
   var y = projection([d.lon, d.lat])[1];
   points.push([x, y]);
});

// bin coords
var bins = hexbin(points);
var bins_n = []; // points per hexbin
bins.forEach(function (d) {
    bins_n.push(d.length);
});

// second of two scales for linear hexagon fill - ref[1]
var extent = d3.extent(bins_n);
var fill_scale2 = d3.scale.linear()
    .domain([extent[0], extent[1]])
    .range([0, 1]);

hex.selectAll(".hexagon")
    .data(hexbin(points))
    .enter()
    .append("path")
    .attr("class", function (d) { return "hexagon bin_" + d.length; })
    .attr("d", hexbin.hexagon())
    .attr("transform", function (d) {
         return "translate(" + d.x + "," + d.y + ")";
     })
     .style("fill", function (d) {
        return fill_scale1(fill_scale2(d.length));
     });

1 个答案:

答案 0 :(得分:0)

我将使用此example建立我的答案。与example相对,此可缩放示例是可缩放的。

投影比例

首先,我将使用上面的示例来说明比例。它使用的投影的起始比例为1/tau:世界的2π弧度被拉伸到一个像素上。平移为[0,0],因此0°N,0°E位于SVG的[0,0]。地图的比例和平移由d3.zoom管理:

projection.scale(transform.k / Math.PI / 2)
  .translate([transform.x, transform.y]);

由于k代表缩放系数,而我们的起始地图宽度为1,k代表地图宽度和高度。除以tau,我们得出地图上每个地球弧度对应多少个像素。翻译将地图居中。

在示例中看不到任何六边形的原因是因为您使用的比例尺将地球延伸到了4194304像素宽(1 << 22)像素,但是六边形仅延伸了一个大小您的SVG。您看不到任何六边形,因为SVG的范围仅代表很小的地理范围-白令海以北的某些海洋区域。

供参考:在所有地图投影上,地图比例尺和地图宽度之间的关系不一致

添加六边形(固定)

如果我们要使用六边形仓位,而仓位保持不变,而不论缩放大小如何,则可以设置半径和范围以反映我们的初始投影(在应用缩放之前):

var hexbin = d3.hexbin()
    .radius(0.01)
    .extent([[-0.5, -0.5], [0.5, 0.5]]); 

然后,我们可以使用初始投影将其传递给投影点,并根据缩放比例变换六边形,同时根据缩放比例缩放六边形的笔触宽度:

var width = Math.max(960, window.innerWidth),
    height = Math.max(500, window.innerHeight);
	
var svg = d3.select("svg")
    .attr("width", width)
    .attr("height", height);
	
// Projection details:
var projection = d3.geoMercator()
    .scale(1 / Math.PI / 2)
    .translate([0, 0]);

var center = projection([0,0]);	

var tile = d3.tile()
    .size([width, height]);

// Zoom details:
var zoom = d3.zoom()
    .scaleExtent([1 << 11, 1 << 14])
    .on("zoom", zoomed);

// Layers for map
var raster = svg.append("g");  // holds tiles
var vector = svg.append("g");  // holds hexagons
var hexes;					   // to hold hexagons

// Hexbin:
var hexbin = d3.hexbin()
    .radius(0.01)
    .extent([[-0.5, -0.5], [0.5, 0.5]]); // extent of the one pixel projection.

var color = d3.scaleLinear()
	.range(["rgba(255,255,255,0.1)","orange"])
    .domain([0, 5]);

d3.json("https://unpkg.com/world-atlas@1/world/110m.json", function(error, world) {
  
 // Create some hexbin data:
 var land = topojson.feature(world, world.objects.land);
 var data = d3.range(500).map(function(d) {
	while(true) {
		var lat = Math.random() * 170 - 70;
		var lon = Math.random() * 360 - 180;
		if(d3.geoContains(land,[lon,lat])) return projection([lon,lat]);
	}
 })
 
 // Create hex bins:
 hexes = vector.selectAll()
  .data(hexbin(data))
  .enter()
  .append("path")
	.attr("d", hexbin.hexagon(0.0085))
  .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
  .attr("fill", function(d) { return color(d.length); })
	.attr("stroke","black")

  svg
   .call(zoom)
   .call(zoom.transform, d3.zoomIdentity
   .translate(width / 2, height / 2)
   .scale(1 << 11)
   .translate(-center[0], -center[1]));
 
});		  
		  
function zoomed() {
  var transform = d3.event.transform;
  
  var tiles = tile
      .scale(transform.k)
      .translate([transform.x, transform.y])
      ();

  // Update projection
  projection
      .scale(transform.k / Math.PI / 2)
      .translate([transform.x, transform.y]);

  // Update vector holding hexes:
  vector.attr("transform","translate("+[transform.x,transform.y]+")scale("+transform.k+")" )
    .attr("stroke-width", 1/transform.k);

  // Update tiles:
  var image = raster
      .attr("transform", stringify(tiles.scale, tiles.translate))
    .selectAll("image")
    .data(tiles, function(d) { return d; });

  image.exit().remove();

  image.enter().append("image")
      .attr("xlink:href", function(d) { return "http://" + "abc"[d[1] % 3] + ".tile.openstreetmap.org/" + d[2] + "/" + d[0] + "/" + d[1] + ".png"; })
      .attr("x", function(d) { return d[0] * 256; })
      .attr("y", function(d) { return d[1] * 256; })
      .attr("width", 256)
      .attr("height", 256);
}

function stringify(scale, translate) {
  var k = scale / 256, r = scale % 1 ? Number : Math.round;
  return "translate(" + r(translate[0] * scale) + "," + r(translate[1] * scale) + ") scale(" + k + ")";
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-tile.v0.0.min.js"></script>
<script src="https://d3js.org/d3-hexbin.v0.2.min.js"></script>
<script src="https://unpkg.com/topojson-client@3"></script>
<svg></svg>

添加六边形(已缩放)

但是,如果我们要在放大代码时更改bin比例,可能会更容易一些,但计算上会更复杂。为此,我们在应用当前投影后根据点的投影坐标重新计算六边形的每次缩放:

var hexbin = d3.hexbin()
 .radius(30)
 .extent([[0,0], [width,height]]) // extent of projected data (displayed)
 .x(function(d) { return projection(d)[0]; })
 .y(function(d) { return projection(d)[1]; })

范围和半径反映了整个SVG范围-应用缩放后将数据投影到的可见范围-要向其添加六边形的范围。下面,我重新计算每个缩放/平移的十六进制:

var width = Math.max(960, window.innerWidth),
    height = Math.max(500, window.innerHeight);
	
var svg = d3.select("svg")
    .attr("width", width)
    .attr("height", height);
	
// Projection details:
var projection = d3.geoMercator()
    .scale(1 / Math.PI / 2)
    .translate([0, 0]);

var center = projection([0,0]);	

var tile = d3.tile()
    .size([width, height]);

// Zoom details:
var zoom = d3.zoom()
    .scaleExtent([1 << 11, 1 << 14])
    .on("zoom", zoomed);

// Layers for map
var raster = svg.append("g");  // holds tiles
var vector = svg.append("g");  // holds hexagons
var hexes;					   // to hold hexagons

// Hexbin:
var hexbin = d3.hexbin()
    .radius(30)
    .extent([[0,0], [width,height]]) // extent of projected data (displayed)
	.x(function(d) { return projection(d)[0]; })
	.y(function(d) { return projection(d)[1]; })

var color = d3.scaleLinear()
	.range(["rgba(255,255,255,0.1)","orange"])
    .domain([0, 5]);
	
var data;

d3.json("https://unpkg.com/world-atlas@1/world/110m.json", function(error, world) {
  
 // Create some hexbin data:
 var land = topojson.feature(world, world.objects.land);
 data = d3.range(500).map(function(d) {
	while(true) {
		var lat = Math.random() * 170 - 70;
		var lon = Math.random() * 360 - 180;
		if(d3.geoContains(land,[lon,lat])) return [lon,lat];
	}
 })
 
  svg
   .call(zoom)
   .call(zoom.transform, d3.zoomIdentity
   .translate(width / 2, height / 2)
   .scale(1 << 11)
   .translate(-center[0], -center[1]));
 
});		  
		  
function zoomed() {
  var transform = d3.event.transform;
  
  var tiles = tile
      .scale(transform.k)
      .translate([transform.x, transform.y])
      ();

  // Update projection
  projection
      .scale(transform.k / Math.PI / 2)
      .translate([transform.x, transform.y]);

  hexes = vector.selectAll("path")
   .data(hexbin(data)) ;
   
   hexes.exit().remove();
   
   hexes.enter()
   .append("path")
   .merge(hexes)
   .attr("d", hexbin.hexagon(29))
   .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
   .attr("fill", function(d) { return color(d.length); })
   .attr("stroke","black")

  // Update tiles:
  var image = raster
      .attr("transform", stringify(tiles.scale, tiles.translate))
    .selectAll("image")
    .data(tiles, function(d) { return d; });

  image.exit().remove();

  image.enter().append("image")
      .attr("xlink:href", function(d) { return "http://" + "abc"[d[1] % 3] + ".tile.openstreetmap.org/" + d[2] + "/" + d[0] + "/" + d[1] + ".png"; })
      .attr("x", function(d) { return d[0] * 256; })
      .attr("y", function(d) { return d[1] * 256; })
      .attr("width", 256)
      .attr("height", 256);
}

function stringify(scale, translate) {
  var k = scale / 256, r = scale % 1 ? Number : Math.round;
  return "translate(" + r(translate[0] * scale) + "," + r(translate[1] * scale) + ") scale(" + k + ")";
}
<svg></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-tile.v0.0.min.js"></script>
<script src="https://d3js.org/d3-hexbin.v0.2.min.js"></script>
<script src="https://unpkg.com/topojson-client@3"></script>

两个示例都随机在陆地上创建一些数据,这是加载缓慢的主要原因

最后的想法

两个示例都需要很多,使用d3-tile和六边形组织坐标空间的方法比可能的要直观一些-可能需要一点时间来习惯。但是,目前没有很多选择。