Hexbins не отображаются на карте гео плитки D3.JS - PullRequest
0 голосов
/ 11 февраля 2019

У меня есть карта, которую я создал с d3-плиткой.Я добавил код, чтобы показать гексбины, собранные из набора данных на основе долготы и широты.Но гексбины не показываются.Я нашел только примеры, которые отображают простые декартовы данные в гексбинах, а не широты и долготы с гексбинами, наложенными на карту, созданную с помощью d3-тайла.Почему мои гексбины не отображаются?

Вот как я определяю проекцию, плитки и гексбины:

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);

А вот как я обрабатываю свои данные и добавляю гексбины на карту:

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 голосов
/ 12 февраля 2019

Я буду использовать этот пример , чтобы построить мой ответ.Этот масштабируемый пример, в отличие от этого примера , является масштабируемым.

Шкала проецирования

Сначала я просто объясню шкалу, используя приведенный выше пример.Он использует проекцию с начальным масштабом 1/tau: 2 π радиана мира растянуты на один пиксель.Перевод равен [0,0], так что 0 ° N, 0 ° E находится в [0,0] SVG.Масштаб и перевод карты управляются d3.zoom:

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

Поскольку k представляет коэффициент масштабирования, а наша начальная ширина карты была 1, k представляет ширину и высоту карты.Делив на тау, мы получаем, сколько пикселей карты соответствуют каждому радиану Земли.Перевод центрирует карту.

Причина, по которой вы не видите гексбин в вашем примере, заключается в том, что вы используете шкалу, которая растягивает землю и имеет площадь 4194304 пикселей (1 << 22), но ваши гексбины растягиваются только надПлощадь размер вашего SVG.Вы не видите шестиугольников, потому что экстент вашего SVG представляет только небольшой географический экстент - некоторую область океана к северу от Берингова моря. </p>

Также для справки: взаимосвязь между масштабом карты и шириной карты несогласованно во всех проекциях карты

Добавление гексбинов (фиксированное значение)

Если мы хотим, чтобы гексагональное объединение с ячейками оставалось одинаковым географическим размером независимо от масштаба, мы можемустановите радиус и экстент, чтобы отразить нашу первоначальную проекцию (перед применением масштабирования):

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>

Добавление hexbins (обновлено с увеличением)

Однако, если мы хотим изменить масштаб ячеек по мере увеличенияв коде потенциально немного проще, но вычислительно сложнее.Для этого мы пересчитываем гексбины каждого масштабирования на основе проецированных координат точек после применения текущей проекции:

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-плиткой и шестиугольниками немного менее интуитивен, чем возможный - и может занять немногопривыкать к.Но на данный момент альтернатив не так много.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...