145 votes

Centrer une carte dans d3 à partir d'un objet geoJSON

Actuellement, dans d3, si vous avez un objet geoJSON que vous allez dessiner, vous devez le mettre à l'échelle et le translater afin de l'obtenir à la taille que l'on veut et le translater afin de le centrer. C'est une tâche très fastidieuse d'essais et d'erreurs, et je me demandais si quelqu'un connaissait une meilleure façon d'obtenir ces valeurs ?

Donc, par exemple, si j'ai ce code

var path, vis, xy;
xy = d3.geo.mercator().scale(8500).translate([0, -1200]);

path = d3.geo.path().projection(xy);

vis = d3.select("#vis").append("svg:svg").attr("width", 960).attr("height", 600);

d3.json("../../data/ireland2.geojson", function(json) {
  return vis.append("svg:g")
    .attr("class", "tracts")
    .selectAll("path")
    .data(json.features).enter()
    .append("svg:path")
    .attr("d", path)
    .attr("fill", "#85C3C0")
    .attr("stroke", "#222");
});

Comment diable puis-je obtenir .scale(8500) et .translate([0, -1200]) sans y aller petit à petit ?

177voto

mbostock Points 25336

Ma réponse est proche de celle de Jan van der Laan, mais vous pouvez simplifier légèrement les choses car vous n'avez pas besoin de calculer le centroïde géographique ; vous n'avez besoin que de la boîte englobante. De plus, en utilisant une projection unitaire sans échelle et sans translation, vous pouvez simplifier les calculs.

La partie importante du code est la suivante :

// Create a unit projection.
var projection = d3.geo.albers()
    .scale(1)
    .translate([0, 0]);

// Create a path generator.
var path = d3.geo.path()
    .projection(projection);

// Compute the bounds of a feature of interest, then derive scale & translate.
var b = path.bounds(state),
    s = .95 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height),
    t = [(width - s * (b[1][0] + b[0][0])) / 2, (height - s * (b[1][1] + b[0][1])) / 2];

// Update the projection to use computed scale & translate.
projection
    .scale(s)
    .translate(t);

Après avoir calculé les boîte de délimitation dans la projection de l'unité, vous pouvez calculer la valeur appropriée échelle en comparant le rapport d'aspect de la boîte englobante ( b[1][0] - b[0][0] et b[1][1] - b[0][1] ) au rapport d'aspect de la toile ( width et height ). Dans ce cas, j'ai également mis à l'échelle la boîte englobante à 95 % de la toile, au lieu de 100 %, de sorte qu'il y a un peu plus de place sur les bords pour les traits et les caractéristiques environnantes ou le remplissage.

Vous pouvez alors calculer le traduire en utilisant le centre de la boîte englobante ( (b[1][0] + b[0][0]) / 2 et (b[1][1] + b[0][1]) / 2 ) et le centre de la toile ( width / 2 et height / 2 ). Notez que puisque la boîte englobante est dans les coordonnées de la projection unitaire, elle doit être multipliée par l'échelle ( s ).

Par exemple, bl.ocks.org/4707858 :

project to bounding box

Il existe une question connexe qui consiste à savoir comment zoomer sur un élément spécifique d'une collection sans ajuster la projection, c'est-à-dire en combinant la projection avec une transformation géométrique pour effectuer un zoom avant et arrière. Cela utilise les mêmes principes que ci-dessus, mais les calculs sont légèrement différents car la transformation géométrique (l'attribut "transform" du SVG) est combinée avec la projection géographique.

Par exemple, bl.ocks.org/4699541 :

zoom to bounding box

138voto

Jan van der Laan Points 2348

Ce qui suit semble faire à peu près ce que vous voulez. La mise à l'échelle semble être correcte. Lorsque je l'applique à ma carte, il y a un petit décalage. Ce petit décalage est probablement dû au fait que j'utilise la commande translate pour centrer la carte, alors que je devrais probablement utiliser la commande center.

  1. Créer une projection et un d3.geo.path
  2. Calculer les limites de la projection actuelle
  3. Utilisez ces bornes pour calculer l'échelle et la translation
  4. Recréer la projection

En code :

  var width  = 300;
  var height = 400;

  var vis = d3.select("#vis").append("svg")
      .attr("width", width).attr("height", height)

  d3.json("nld.json", function(json) {
      // create a first guess for the projection
      var center = d3.geo.centroid(json)
      var scale  = 150;
      var offset = [width/2, height/2];
      var projection = d3.geo.mercator().scale(scale).center(center)
          .translate(offset);

      // create the path
      var path = d3.geo.path().projection(projection);

      // using the path determine the bounds of the current map and use 
      // these to determine better values for the scale and translation
      var bounds  = path.bounds(json);
      var hscale  = scale*width  / (bounds[1][0] - bounds[0][0]);
      var vscale  = scale*height / (bounds[1][1] - bounds[0][1]);
      var scale   = (hscale < vscale) ? hscale : vscale;
      var offset  = [width - (bounds[0][0] + bounds[1][0])/2,
                        height - (bounds[0][1] + bounds[1][1])/2];

      // new projection
      projection = d3.geo.mercator().center(center)
        .scale(scale).translate(offset);
      path = path.projection(projection);

      // add a rectangle to see the bound of the svg
      vis.append("rect").attr('width', width).attr('height', height)
        .style('stroke', 'black').style('fill', 'none');

      vis.selectAll("path").data(json.features).enter().append("path")
        .attr("d", path)
        .style("fill", "red")
        .style("stroke-width", "1")
        .style("stroke", "black")
    });

54voto

Paulo Scardine Points 17518

Je suis nouveau dans la d3 - je vais essayer d'expliquer comment je comprends, mais je ne suis pas sûr d'avoir tout compris.

Le secret est de savoir que certaines méthodes vont opérer sur l'espace cartographique (latitude,longitude) et d'autres sur l'espace cartésien (x,y sur l'écran). L'espace cartographique (notre planète) est (presque) sphérique, l'espace cartésien (écran) est plat - pour faire correspondre l'un à l'autre, vous avez besoin d'un algorithme, qui est appelé projection . Cet espace est trop court pour approfondir le sujet fascinant des projections et la manière dont elles déforment les caractéristiques géographiques afin de transformer la sphère en plan ; certains sont conçus pour conserver les angles, d'autres les distances et ainsi de suite - il y a toujours un compromis (Mike Bostock a une une énorme collection d'exemples ).

enter image description here

Dans d3, l'objet de projection a une propriété/un paramètre de centre, donné en unités de carte :

projection.center([location])

Si center est spécifié, définit le centre de la projection à l'emplacement spécifié, un tableau à deux éléments de longitude et de latitude en degrés et renvoie la projection. Si center n'est pas spécifié, renvoie le centre actuel qui a pour valeur par défaut ⟨0°,0°⟩.

Il y a aussi la translation, donnée en pixels - où se trouve le centre de projection par rapport à la toile :

projection.translate([point])

Si point est spécifié, définit le décalage de translation de la projection dans le tableau à deux éléments [x, y] spécifié et renvoie la projection. Si point n'est pas spécifié, renvoie le décalage de translation actuel, qui est par défaut de [480, 250]. Le décalage de translation détermine les coordonnées en pixels du centre de la projection. Le décalage de translation par défaut place ⟨0°,0°⟩ au centre d'une zone de 960×500.

Lorsque je veux centrer un élément dans le canevas, je préfère définir le centre de projection au centre de la boîte de délimitation de l'élément. mercator (WGS 84, utilisé dans google maps) pour mon pays (Brésil), jamais testé avec d'autres projections et hémisphères. Vous devrez peut-être faire des ajustements pour d'autres situations, mais si vous respectez ces principes de base, tout ira bien.

Par exemple, étant donné une projection et un chemin :

var projection = d3.geo.mercator()
    .scale(1);

var path = d3.geo.path()
    .projection(projection);

Le site bounds méthode de path renvoie la boîte de délimitation en pixels . Utilisez-le pour trouver l'échelle correcte, en comparant la taille en pixels avec la taille en unités cartographiques (0,95 vous donne une marge de 5% par rapport à la meilleure adaptation pour la largeur ou la hauteur). Géométrie de base ici, calcul de la largeur/hauteur d'un rectangle avec des coins diagonalement opposés :

var b = path.bounds(feature),
    s = 0.95 / Math.max(
                   (b[1][0] - b[0][0]) / width, 
                   (b[1][1] - b[0][1]) / height
               );

enter image description here

Utilisez le d3.geo.bounds pour trouver la boîte de délimitation en unités cartographiques :

b = d3.geo.bounds(feature);

Définissez le centre de la projection au centre de la boîte de délimitation :

projection.center([(b[1][0]+b[0][0])/2, (b[1][1]+b[0][1])/2]);

Utilisez le translate pour déplacer le centre de la carte vers le centre de la toile :

projection.translate([width/2, height/2]);

À présent, l'élément doit se trouver au centre de la carte, avec une marge de 5 %.

4voto

Dave Foster Points 123

Il existe un centre() que vous pouvez utiliser et qui accepte une paire lat/lon.

D'après ce que j'ai compris, translate() n'est utilisé que pour déplacer littéralement les pixels de la carte. Je ne suis pas sûr de savoir comment déterminer ce qu'est l'échelle.

1voto

Kris Carle Points 406

Pour effectuer un panoramique/zoom sur la carte, vous devriez envisager de superposer le SVG sur Leaflet. Ce sera beaucoup plus facile que de transformer le SVG. Voir cet exemple http://bost.ocks.org/mike/leaflet/ et ensuite Comment changer le centre de la carte dans le dépliant

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X