Centrer une carte en d3 avec un objet geoJSON

actuellement en d3 si vous avez un objet geoJSON que vous allez dessiner, vous devez le dimensionner et le traduire afin de l'obtenir à la taille que l'on veut et de le traduire afin de le centrer. C'est un très fastidieuse tâche 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 puis-je obtenir .échelle(8500) et .traduire([0, -1200]) sans aller petit à petit?

125
demandé sur Hugolpz 2013-01-24 05:11:10

11 réponses

semble faire environ ce que vous voulez. La mise à l'échelle semble être correcte. En l'appliquant à ma carte il y a un petit décalage. Ce petit décalage est probablement causé parce 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 d3.geo.chemin
  2. Calculez les limites de la projection actuelle
  3. utilisez ces bornes pour calculer l'échelle et la traduction", 151950920"
  4. recréer la projection

dans le 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")
    });
115
répondu Jan van der Laan 2013-02-01 21:02:20

ma réponse est proche de celle de Jan van der Laan, mais vous pouvez simplifier légèrement les choses parce que vous n'avez pas besoin de calculer le centroïde géographique; vous n'avez besoin que de la boîte de délimitation. Et, en utilisant une projection unitaire non traduite, vous pouvez simplifier le calcul.

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 le calcul de la caractéristique bounding box dans la projection de l'unité, vous pouvez calculer approprié échelle en comparant le rapport d'aspect de la boîte limite ( 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-ci, j'ai aussi réduit la boîte limite à 95% de la toile, plutôt qu'à 100%, donc il y a un peu d'espace supplémentaire sur les bords pour les traits et les caractéristiques environnantes ou le rembourrage.

alors vous pouvez 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 ). Étant donné que la zone de délimitation est située 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 ya une question connexe où est la façon de zoomer à une caractéristique spécifique d'une collection sans ajustement de la projection, i.e. , combinant la projection avec une transformation géométrique pour zoomer et zoomer. Cela utilise les mêmes principes que ci-dessus, mais le calcul est légèrement différent parce que la transformation géométrique (l'attribut SVG "transform") est combinée avec la projection géographique.

par exemple, bl.ocks.org/4699541 :

zoom to bounding box

157
répondu mbostock 2013-09-03 21:27:17

je suis nouveau à d3 - va essayer d'expliquer comment je l'ai compris, mais je ne suis pas sûr que j'ai tout ce qu'il faut.

Le secret est de savoir que certaines méthodes fonctionnent sur la cartographique de l'espace (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 cartographier l'un sur l'autre vous avez besoin d'un algorithme, qui est appelé projection . certains sont conçus pour conserver les angles, d'autres pour conserver les distances et ainsi de suite - il y a toujours un compromis (Mike Bostock a un énorme collection d'exemples ).

enter image description here

dans d3, l'objet de projection a une propriété de Centre / setter, donné en unités de carte:

de projection.centre([lieu])

si le centre est spécifié, fixe 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 le centre n'est pas spécifié, renvoie le centre actuel qui est en défaut à l'option 0°, 0°.

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

de projection.traduire ([point])

si le point est spécifié, fixe le décalage de translation de la projection au tableau à deux éléments spécifié [x, y] et renvoie la projection. Si point n'est pas spécifié, renvoie le décalage de traduction courant qui par défaut est [480, 250]. Le décalage de traduction détermine les coordonnées pixel du centre de la projection. Défaut traduction offset place l'eventualité 0°,0° au centre d'une zone 960×500.

quand je veux centrer une caractéristique dans la toile, j'aime mettre le centre de projection au centre de la boîte de limite de la caractéristique - cela fonctionne pour moi en utilisant mercator (WGS 84, utilisé dans Google maps) pour mon pays (Brésil), jamais testé en utilisant d'autres projections et hémisphères. Vous pouvez avoir à faire des ajustements pour d'autres situations, mais si vous clouez ces principes, vous serez bien.

par exemple, compte tenu d'une projection et d'une trajectoire:

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

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

la méthode bounds de path renvoie la case limite en pixels . Utilisez - le pour trouver l'échelle correcte, en comparant la taille en pixels avec la taille en unités de carte (0,95 vous donne une marge de 5% sur le meilleur ajustement pour la largeur ou la hauteur). Géométrie de base ici, en calculant le rectangle Largeur / Hauteur donné diagonalement opposé coins:

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

enter image description here

utilisez la méthode d3.geo.bounds pour trouver la zone de délimitation dans les unités cartographiques:

b = d3.geo.bounds(feature);

placer le centre de la projection au centre de la boîte de limite:

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

Utiliser le translate méthode pour déplacer le centre de la carte au centre de la toile:

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

maintenant vous devriez avoir la fonctionnalité au centre de la carte zoomé avec une marge de 5%.

48
répondu Paulo Scardine 2016-08-10 12:44:18

avec d3 v4 c'est beaucoup plus facile!

var projection = d3.geoMercator().fitSize([width, height], geojson);
var path = d3.geoPath().projection(projection);

et enfin

g.selectAll('path')
  .data(geojson.features)
  .enter()
  .append('path')
  .attr('d', path)
  .style("fill", "red")
  .style("stroke-width", "1")
  .style("stroke", "black");

Profiter De, Cheers

26
répondu dnltsk 2016-12-02 20:02:38

il y a une méthode center () que vous pouvez utiliser 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 sais pas comment déterminer ce qu'est l'échelle.

4
répondu Dave Foster 2013-01-29 21:31:08

j'ai cherché sur Internet un moyen facile de centrer ma carte, et je me suis inspiré de la réponse de Jan van der Laan et mbostock. Voici un moyen plus simple d'utiliser jQuery si vous utilisez un conteneur pour la svg. J'ai créé une bordure de 95% pour le rembourrage/borders etc.

var width = $("#container").width() * 0.95,
    height = $("#container").width() * 0.95 / 1.9 //using height() doesn't work since there's nothing inside

var projection = d3.geo.mercator().translate([width / 2, height / 2]).scale(width);
var path = d3.geo.path().projection(projection);

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

si vous cherchez une échelle exacte, cette réponse ne fonctionnera pas pour vous. Mais si comme moi, vous souhaitez afficher une carte qui centralise dans un récipient, cela devrait être suffisant. J'ai essayé de affichage de la carte mercator et a constaté que cette méthode était utile pour centraliser ma carte, et je pourrais facilement couper la partie Antarctique puisque je n'en avais pas besoin.

2
répondu Wei Hao 2013-06-10 08:24:42

pan/zoom sur la carte, vous devriez regarder la superposition du SVG sur la Notice. 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 la notice

1
répondu Kristofor Carle 2017-05-23 12:02:47

en plus de centrer une carte dans d3 donné un objet geoJSON , notez que vous pouvez préférer fitExtent() plutôt que fitSize() si vous voulez spécifier un rembourrage autour des limites de votre objet. fitSize() règle automatiquement ce rembourrage à 0.

1
répondu Sylvain Lesage 2018-04-01 12:07:15

avec la réponse de mbostocks, et le commentaire de Herb Caudill, j'ai commencé à rencontrer des problèmes avec L'Alaska depuis que j'utilisais une projection mercator. Je dois noter que pour mes propres fins, j'essaie de projeter et de centrer les États-Unis. J'ai trouvé que je devais marier les deux réponses avec la réponse de Jan van der Laan à l'exception suivante pour les polygones qui se chevauchent hémisphères (polygones qui finissent avec une valeur absolue Pour Est-Ouest qui est supérieure à 1):

  1. mettre en place une projection simple dans mercator:

    projection = d3.geo.Mercator.)(l'échelle(1).traduire([0,0]);

  2. créer le chemin:

    path = d3.geo.chemin.)(la projection(la projection);

3.

var bounds = path.bounds(topoJson),
  dx = Math.abs(bounds[1][0] - bounds[0][0]),
  dy = Math.abs(bounds[1][1] - bounds[0][1]),
  x = (bounds[1][0] + bounds[0][0]),
  y = (bounds[1][1] + bounds[0][1]);

4.Ajouter une exception pour L'Alaska et les États qui chevauchent les hémisphères:

if(dx > 1){
var center = d3.geo.centroid(topojson.feature(json, json.objects[topoObj]));
scale = height / dy * 0.85;
console.log(scale);
projection = projection
    .scale(scale)
    .center(center)
    .translate([ width/2, height/2]);
}else{
scale = 0.85 / Math.max( dx / width, dy / height );
offset = [ (width - scale * x)/2 , (height - scale * y)/2];

// new projection
projection = projection                     
    .scale(scale)
    .translate(offset);
}

j'espère que cela aidera.

0
répondu DimeZilla 2016-01-06 18:16:25

pour les gens qui veulent s'ajuster verticalement et horizontalement, voici la solution:

  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);

      // adjust projection
      var bounds  = path.bounds(json);
      offset[0] = offset[0] + (width - bounds[1][0] - bounds[0][0]) / 2;
      offset[1] = offset[1] + (height - bounds[1][1] - bounds[0][1]) / 2;

      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")
    });
0
répondu pcmanprogrammeur 2016-02-13 12:06:58

comment j'ai centré un Topojson, où j'avais besoin de retirer la fonctionnalité:

      var projection = d3.geo.albersUsa();

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


      var tracts = topojson.feature(mapdata, mapdata.objects.tx_counties);

      projection
          .scale(1)
          .translate([0, 0]);

      var b = path.bounds(tracts),
          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];

      projection
          .scale(s)
          .translate(t);

        svg.append("path")
            .datum(topojson.feature(mapdata, mapdata.objects.tx_counties))
            .attr("d", path)
0
répondu in code veritas 2016-03-31 21:18:44