Centre un mapa en d3 dado un objeto geoJSON

Actualmente, en d3, si tiene un objeto geoJSON que va a dibujar, tiene que escalarlo y traducirlo para obtener el tamaño que desee y traducirlo para centrarlo. Esta es una tarea muy tediosa de prueba y error, y me preguntaba si alguien conocía una mejor manera de obtener estos valores.

Entonces, por ejemplo, si tengo este código

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

¿Cómo demonios puedo obtener .scale (8500) y .translate ([0, -1200]) sin ir poco a poco?

    Lo siguiente parece hacer aproximadamente lo que quieres. La escala parece estar bien. Al aplicarlo a mi mapa, hay una pequeña compensación. Esta pequeña compensación probablemente se debe a que utilizo el comando traducir para centrar el mapa, mientras que probablemente debería usar el comando central.

    1. Crea una proyección y d3.geo.path
    2. Calcule los límites de la proyección actual
    3. Use estos límites para calcular la escala y la traducción
    4. Recrea la proyección

    En codigo:

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

    Mi respuesta está cerca de la de Jan van der Laan, pero puedes simplificar un poco las cosas porque no necesitas calcular el centroide geográfico; solo necesitas el cuadro delimitador. Y, al usar una proyección de unidad no traducida y sin escalar, puede simplificar las operaciones matemáticas.

    La parte importante del código es esto:

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

    Después de comstackr el cuadro delimitador de la función en la proyección de la unidad, puede calcular la escala adecuada comparando la relación de aspecto del cuadro delimitador ( b[1][0] - b[0][0] b[1][1] - b[0][1] ) a la relación de aspecto del canvas ( width y height ). En este caso, también he escalado el cuadro delimitador al 95% del canvas, en lugar de al 100%, por lo que hay un poco más de espacio en los bordes para los trazos y las características circundantes o el relleno.

    Luego puedes calcular la traducción usando el centro del cuadro delimitador ( (b[1][0] + b[0][0]) / 2 y (b[1][1] + b[0][1]) / 2 ) y el centro del canvas ( width / 2 y height / 2 ). Tenga en cuenta que dado que el cuadro delimitador se encuentra en las coordenadas de proyección de la unidad, debe multiplicarse por la ( s ) escala ( s ).

    Por ejemplo, bl.ocks.org/4707858 :

    proyecto al cuadro delimitador

    Hay una pregunta relacionada en la que se explica cómo acercar una característica específica de una colección sin ajustar la proyección, es decir , combinar la proyección con una transformación geométrica para acercar y alejar. Utiliza los mismos principios que antes, pero la matemática es ligeramente diferente porque la transformación geométrica (el atributo SVG “transformar”) se combina con la proyección geográfica.

    Por ejemplo, bl.ocks.org/4699541 :

    acercar al cuadro delimitador

    Soy nuevo en d3: intentaré explicar cómo lo entiendo, pero no estoy seguro de haberlo hecho bien.

    El secreto es saber que algunos métodos operarán en el espacio cartográfico (latitud, longitud) y otros en el espacio cartesiano (x, y en la pantalla). El espacio cartográfico (nuestro planeta) es (casi) esférico, el espacio cartesiano (pantalla) es plano: para mapear uno sobre otro, se necesita un algoritmo, que se llama proyección . Este espacio es demasiado corto para profundizar en el fascinante tema de las proyecciones y la forma en que distorsionan las características geográficas para convertir esférica en plano; algunos están diseñados para conservar angularjs, otros conservan distancias, etc., siempre hay un compromiso (Mike Bostock tiene una gran colección de ejemplos ).

    enter image description here

    En d3, el objeto de proyección tiene una propiedad / setter central, dada en unidades de mapa:

    projection.center ([ubicación])

    Si se especifica center, establece el centro de la proyección en la ubicación especificada, una matriz de dos elementos de longitud y latitud en grados y devuelve la proyección. Si no se especifica el centro, devuelve el centro actual que por defecto es ⟨0 °, 0 °⟩.

    También está la traducción, en píxeles, donde el centro de proyección se encuentra en relación con el canvas:

    projection.translate ([punto])

    Si se especifica punto, establece el desplazamiento de la traducción de la proyección en la matriz de dos elementos especificada [x, y] y devuelve la proyección. Si no se especifica el punto, devuelve el desplazamiento de traducción actual que por defecto es [480, 250]. El desplazamiento de la traducción determina las coordenadas de píxel del centro de la proyección. El desplazamiento de conversión predeterminado coloca ⟨0 °, 0 °⟩ en el centro de un área de 960 × 500.

    Cuando quiero centrar una función en el canvas, me gusta configurar el centro de proyección en el centro del cuadro delimitador de funciones; esto funciona para mí cuando uso mercator (WGS 84, usado en google maps) para mi país (Brasil), nunca probado usando otras proyecciones y hemisferios. Es posible que tenga que hacer ajustes para otras situaciones, pero si descubre estos principios básicos, estará bien.

    Por ejemplo, dada una proyección y una ruta:

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

    El método de bounds de la path devuelve el cuadro delimitador en píxeles . Úselo para encontrar la escala correcta, comparando el tamaño en píxeles con el tamaño en unidades de mapa (0.95 le da un margen de 5% sobre el mejor ajuste para ancho o alto). Geometría básica aquí, calculando el ancho / alto del rectángulo en esquinas diagonales opuestas:

     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

    Utilice el método d3.geo.bounds para buscar el cuadro delimitador en unidades de mapa:

     b = d3.geo.bounds(feature); 

    Establezca el centro de la proyección en el centro del cuadro delimitador:

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

    Use el método de translate para mover el centro del mapa al centro del canvas:

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

    A estas alturas, deberías tener la función en el centro del mapa ampliada con un margen del 5%.

    ¡Con d3 v4 es cada vez más fácil!

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

    y finalmente

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

    Disfruta, Saludos

    Hay un método center () que puede usar que acepta un par lat / lon.

    Por lo que entiendo, translate () solo se usa para mover literalmente los píxeles del mapa. No estoy seguro de cómo determinar qué escala es.

    Estaba buscando en Internet una manera fácil de centrar mi mapa, y me inspiré en la respuesta de Jan van der Laan y mbostock. Esta es una forma más fácil de usar jQuery si está usando un contenedor para svg. Creé un borde del 95% para relleno / bordes, 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 busca una escala exacta, esta respuesta no funcionará para usted. Pero si, como yo, desea mostrar un mapa que se centralice en un contenedor, esto debería ser suficiente. Estaba tratando de mostrar el mapa de Mercator y encontré que este método era útil para centralizar mi mapa, y podía cortar fácilmente la parte de la Antártida ya que no la necesitaba.

    Para desplazarse / hacer zoom en el mapa, debe ver la superposición del SVG en el Folleto. Eso será mucho más fácil que transformar el SVG. Ver este ejemplo http://bost.ocks.org/mike/leaflet/ y luego Cómo cambiar el centro del mapa en un folleto

    Además de centrar un mapa en d3 dado un objeto geoJSON , tenga en cuenta que puede preferir fitExtent() sobre fitSize() si desea especificar un relleno alrededor de los límites de su objeto. fitSize() establece automáticamente este relleno en 0.

    Con la respuesta de Mbostocks, y el comentario de Herb Caudill, comencé a tener problemas con Alaska porque estaba usando una proyección Mercator. Debo señalar que, para mis propios fines, bash proyectar y centrar los Estados de EE. UU. Descubrí que tenía que unir las dos respuestas con la respuesta de Jan van der Laan con la siguiente excepción para los polígonos que se superponen a los hemisferios (polígonos que terminan con un valor absoluto para Este – Oeste que es mayor que 1):

    1. configurar una proyección simple en mercator:

      proyección = d3.geo.mercator (). scale (1) .translate ([0,0]);

    2. crea la ruta:

      path = d3.geo.path (). proyección (proyección);

    3. configurar mis límites:

     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. Agregue la excepción para Alaska y los estados que se superponen a los hemisferios:

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

    Espero que esto ayude.

    Para las personas que desean ajustar verticalmente y horizontaly, aquí está la solución:

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

    Cómo centré un Topojson, donde necesitaba sacar la función:

      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)