Création d'une carte D3 de données d'enveloppe d'ellipse


16

J'ai cet ensemble de données qui a des ellipses, plus spécifiquement des "enveloppes" d'ellipse. Je me demandais si quelqu'un avait des conseils sur la façon de les dessiner sur une carte D3. J'ai déjà une configuration de carte avec projection Mercator. Cette réponse de stackoverflow a une fonction createEllipse qui m'a rapproché, mais je veux m'assurer d'interpréter correctement les données.

J'ai branché les valeurs des axes majeurs / mineurs de l'ellipse à partir des données et utilisé l'azimut pour la rotation, est-ce correct? Je ne comprends pas non plus vraiment la partie "enveloppe". Comment plusieurs ellipses dans chaque zone créent-elles une seule forme contiguë?

Tout avis sera le bienvenu.

entrez la description de l'image ici

  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>

Réponses:


1

Il semble que vous interprétez les résultats presque correctement.

Une erreur que j'ai corrigée est que votre code ne prend pas en compte l'azimut.

Un autre problème possible peut être lié aux axes. Dans le tableau fourni, elles sont nommées comme "dimensions d'axe" qui sonnent comme des dimensions d'ellipse, tandis que la fonction createEllipse prend des rayons comme paramètres. S'il vous plaît, jetez un œil à la visualisation zoomée avec les problèmes mentionnés ci-dessus corrigés. Une info-bulle au survol est ajoutée pour la référence.

Le troisième problème est discutable et dépend du format de données établi dans le tableau. Je veux dire que x ne signifie pas toujours la longitude et la latitude y. Mais logiquement, il semble que les ellipses des valeurs plus longues (les valeurs "x" sont plus grandes ou égales aux valeurs "y") devraient correspondre à la direction horizontale.

En remarque: la précision de la visualisation est également affectée par l'utilisation du rayon de la Terre approximatif mais c'est mineur.

Par «enveloppe», on entend probablement ici que l'ellipse contourne une certaine zone d'intérêt qui se trouve à l'intérieur, étant donné que les valeurs de zone données sont beaucoup plus petites que la zone d'ellipse.


Cela aide énormément. Merci pour la réponse et l'exemple de code! Je reçois plus d'informations sur l'ensemble de données. (Ce sont des données sur la chute de débris de fusée) Donc, je crois que l'enveloppe est la région dans laquelle toutes les ellipses sont contenues.
Jrue
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.