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?
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.
- créer une projection et d3.geo.chemin
- Calculez les limites de la projection actuelle
- utilisez ces bornes pour calculer l'échelle et la traduction", 151950920"
- 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")
});
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 :
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 :
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 ).
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);
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%.
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
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.
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.
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
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.
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):
-
mettre en place une projection simple dans mercator:
projection = d3.geo.Mercator.)(l'échelle(1).traduire([0,0]);
-
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.
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")
});
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)