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 stackoverflow a une fonction createEllipse qui m'a rapproché, mais je veux m'assurer que j'interprète 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, serait-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.
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>
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 au zoom avant visualisation 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 valeurs ellipses 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.