Criando mapa D3 dos dados do envelope da elipse

16

Eu tenho esse conjunto de dados que possui elipses, mais especificamente elipse "envelopes". Eu queria saber se alguém tinha conselhos sobre como eu poderia desenhá-los em um mapa D3. Eu já tenho uma configuração de mapa com projeção mercator. Esta resposta do stackoverflow possui uma função createEllipse que me aproximou, mas quero ter certeza de que estou interpretando os dados corretamente.

Conectei os valores do eixo maior / menor da elipse a partir dos dados e usei o azimute para a rotação, isso estaria correto? Eu também realmente não entendo a parte "envelope". Como várias elipses em cada zona criam uma única forma contígua?

Qualquer conselho seria apreciado.

insira a descrição da imagem aqui

  const margin  = {top:0, right:0, bottom:0, left:0},
        width   = 1000 - margin.left - margin.right,
        height  = 800  - margin.top - margin.bottom;

  const svg = d3.select('body')
      .append('svg')
      .attr('width', '100%')
      .attr('height', '100%')
      .attr('viewBox', `0 0 ${width + margin.left + margin.right} ${height + margin.top + margin.bottom}`);

  const chart = svg.append('g')
      .attr('transform', `translate(${margin.left},${margin.top})`);

  //a/b are ellipse axes, x/y is center
  const createEllipse = function createEllipse(a, b, x = 0, y = 0, rotation = 0) {
    let k = Math.ceil(36 * (Math.max(a/b,b/a))); // sample angles
    let coords = [];
    for (let i = 0; i <= k; i++) {
      let angle = Math.PI*2 / k * i + rotation;
      let r = a * b / Math.sqrt(a*a*Math.sin(angle)*Math.sin(angle) + b*b*Math.cos(angle)*Math.cos(angle));
      coords.push(getLatLong([x,y],angle,r));
    }
    return { 'type':'Polygon', 'coordinates':[coords] };
  }

  const getLatLong = function getLatLong(center,angle,radius) {
    let rEarth = 6371; // kilometers
    x0 = center[0] * Math.PI / 180; // convert to radians.
    y0 = center[1] * Math.PI / 180;
    let y1 = Math.asin( Math.sin(y0)*Math.cos(radius/rEarth) + Math.cos(y0)*Math.sin(radius/rEarth)*Math.cos(angle) );
    let x1 = x0 + Math.atan2(Math.sin(angle)*Math.sin(radius/rEarth)*Math.cos(y0), Math.cos(radius/rEarth)-Math.sin(y0)*Math.sin(y1));
    y1 = y1 * 180 / Math.PI;
    x1 = x1 * 180 / Math.PI;
    return [x1,y1];
  } 


  d3.json('https://media.journalism.berkeley.edu/upload/2019/11/kazakhstan.json').then((data) => {

      const ellipses = [
        {lat: 48.6,    lng: 64.7,     axis_x: 30, axis_y: 16, azimuth: 26.5, area_hectar: 0.0713,  zone: 'U1'},
        {lat: 48.625,  lng: 64.625,   axis_x: 30, axis_y: 16, azimuth: 26.5, area_hectar: 0.0713,  zone: 'U1'},
        {lat: 48.366,  lng: 65.44166, axis_x: 50, axis_y: 30, azimuth: 40,   area_hectar: 0.11775, zone: 'U2'},
        {lat: 48.85,   lng: 65.61666, axis_x: 20, axis_y: 22, azimuth: 29,   area_hectar: 0.17584, zone: 'U3'},
        {lat: 48.9333, lng: 65.8,     axis_x: 22, axis_y: 22, azimuth: 28,   area_hectar: 0.17584, zone: 'U3'},
        {lat: 48.9166, lng: 66.05,    axis_x: 50, axis_y: 20, azimuth: 38,   area_hectar: 0.17584, zone: 'U3'},
        {lat: 48.9166, lng: 65.68333, axis_x: 20, axis_y: 22, azimuth: 29,   area_hectar: 0.17584, zone: 'U3'},
        {lat: 49,      lng: 65.86666, axis_x: 22, axis_y: 22, azimuth: 29,   area_hectar: 0.17584, zone: 'U3'}
      ]

      const projection = d3.geoMercator()
        .fitExtent([[0,0],[width,height]], data)

      const path = d3.geoPath()
        .projection(projection);


      chart.selectAll('path')
        .data(data.features)
        .enter()
        .append('path')
        .attr('d',  path)
        .attr('stroke', 'black')
        .attr('strok-width', '1px')
        .attr('fill', 'none');

      chart.selectAll(".ellipses")
        .data(ellipses.map((d) => createEllipse(d.axis_x, d.axis_y, d.lng, d.lat, d.azimuth)))
        .enter()
        .append('path')
        .attr('d', path)
        .attr('stroke', 'black')
        .attr('stroke-width', '1px')
        .attr('fill', 'orange');

  });
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="chart"></div>

jrue
fonte

Respostas:

1

Parece que você está interpretando os resultados quase corretamente.

Um erro que eu corrigi é que seu código não considera o azimute.

Outra questão possível pode estar relacionada aos eixos. Na tabela fornecida, eles são nomeados como "dimensões do eixo", que soam como dimensões da elipse, enquanto a função createEllipse recebe raios como parâmetros. Por favor, dê uma olhada na visualização ampliada com os problemas mencionados acima corrigidos. A dica de ferramenta ao passar o mouse é adicionada para a referência.

A terceira questão é discutível e depende do formato de dados estabelecido na tabela. Quero dizer que x nem sempre significa longitude e latitude y. Mas, logicamente, parece que os valores mais longos das reticências (valores "x" são maiores ou iguais aos valores "y") devem corresponder à direção horizontal.

Como uma observação lateral: a precisão da visualização também é afetada pelo uso do raio aproximado da Terra, mas isso é menor.

Por "envelope", aqui, provavelmente se entende que a elipse circunscreve certa área de interesse que se encontra dentro, considerando o fato de que os valores de área fornecidos são muito menores que a área da elipse.

Anbu Agarwal
fonte
Isso ajuda imensamente. Obrigado pela resposta e pelo exemplo de código! Estou recebendo mais informações sobre o conjunto de dados. (São dados sobre a queda de detritos de foguetes). Então, acredito que o envelope é a região na qual todas as elipses estão contidas.
Jrue