Crear un mapa D3 de datos de envolvente de elipse

16

Tengo este conjunto de datos que tiene puntos suspensivos, más específicamente "sobres" de elipse. Me preguntaba si alguien tenía consejos sobre cómo podría dibujarlos en un mapa D3. Ya tengo una configuración de mapa con proyección de mercator. Esta respuesta de stackoverflow tiene una función createEllipse que me acercó, pero quiero asegurarme de que estoy interpretando los datos correctamente.

Conecté los valores del eje mayor / menor de la elipse a partir de los datos, y usé el acimut para la rotación, ¿sería correcto? Tampoco entiendo realmente la parte del "sobre". ¿Cómo varias elipses en cada zona crean una sola forma contigua?

Cualquier consejo sería apreciado.

ingrese la descripción de la imagen aquí

  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
fuente

Respuestas:

1

Parece que está interpretando los resultados casi correctamente.

Un error que solucioné es que su código no considera el acimut.

Otro posible problema puede estar relacionado con los ejes. En la tabla proporcionada, se denominan "dimensiones del eje" que suenan como dimensiones de una elipse, mientras que la función createEllipse toma los radios como parámetros. Por favor, eche un vistazo a la visualización ampliada con los problemas mencionados anteriormente solucionados. Se agrega información sobre herramientas al pasar el mouse por referencia.

El tercer problema es discutible y depende del formato de datos establecido en la tabla. Quiero decir que x no siempre significa longitud e y - latitud. Pero lógicamente parece que los puntos suspensivos más largos (los valores "x" son mayores o iguales que los valores "y") deberían corresponder a la dirección horizontal.

Como nota al margen: la precisión de la visualización también se ve afectada por el uso del radio aproximado de la Tierra, pero eso es menor.

Probablemente, por "envolvente" se entiende que la elipse circunscribe cierta área de interés que se encuentra dentro, considerando el hecho de que los valores de área dados son mucho más pequeños que el área de la elipse.

Anbu Agarwal
fuente
Esto ayuda inmensamente. ¡Gracias por la respuesta y el código de muestra! Estoy obteniendo más información sobre el conjunto de datos. (Son datos sobre restos de cohetes que caen) Entonces, creo que el sobre es la región en la que están contenidas todas las elipses.
Jueves