Escalar el mapa d3 v4 para que se ajuste a SVG (o nada)
Estoy intentando hacer este mapa de Estados Unidos a escala más pequeña. Ya sea a mi SVG, o incluso manualmente.
Este es mi código en su forma más simple:
function initializeMapDifferent(){
var svg = d3.select("#map").append("svg")
.attr("width", 1000)
.attr("height", 500);
d3.json("https://d3js.org/us-10m.v1.json", function (error, us){
svg.append("g")
.attr("class", "states")
.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("fill", "gray")
.attr("d", d3.geoPath());
});
}
He probado algo como:
var path = d3.geoPath()
.projection(d3.geoConicConformal()
.parallels([33, 45])
.rotate([96, -39])
.fitSize([width, height], conus));
pero cada vez que agrego algo a mi variable de ruta obtengo errores de NAN de las partes internas de D3. ¡Gracias por cualquier ayuda!
Por qué los datos no se proyectan correctamente
La cuestión clave es que sus datos ya están proyectados. Las geoproyecciones D3 utilizan datos que no están proyectados o en pares largos de latitud. Datos en el datum WGS84. Básicamente, una geoProjection d3 toma coordenadas esféricas y las traduce a coordenadas cartesianas planas x,y.
Sus datos no se ajustan a esto: ya son planos. Se puede ver claramente porque Alaska no está donde debería estar (a menos que alguien haya cambiado los últimos pares de Alaska, lo cual es poco probable). Otros signos y síntomas de datos ya proyectados pueden ser una característica que cubre todo el planeta y errores de NaN.
El hecho de que se trate de una proyección compuesta dificulta la desproyección, pero puede mostrar datos ya proyectados en d3.js.
"Proyectando" datos ya proyectados
Proyección nula:
De manera más simple, puedes definir tu proyección como nula:
var path = d3.geoPath(null);
Esto tomará los datos x,y de las geometrías geojson y los mostrará como datos x,y. Sin embargo, si sus coordenadas x,y exceden el ancho y el alto de su svg, el mapa no estará contenido dentro de su svg (como encontró en su ejemplo con .attr("d", d3.geoPath());
).
El archivo particular en esta pregunta está preproyectado para ajustarse a un mapa de 960x600, por lo que es ideal para una proyección nula: fue diseñado teniendo en cuenta las dimensiones. Sus unidades son píxeles y todas las coordenadas se encuentran dentro de las dimensiones deseadas. Sin embargo, la mayoría de las geometrías proyectadas utilizan sistemas de coordenadas con unidades como metros, de modo que el cuadro delimitador de las coordenadas de la entidad puede tener millones de unidades de ancho. En estos casos, la proyección nula no funcionará: convertirá un valor de unidad de mapa en un valor de píxel sin escala.
Con d3, se usa comúnmente una proyección nula con geojson/topojson que está preproyectada para ajustarse a una ventana gráfica específica usando una proyección d3. Consulte cartografía de línea de comando para ver un ejemplo (el ejemplo utiliza archivos fuente no proyectados; los mismos problemas que surgen al usar una proyección d3 en datos proyectados se aplican tanto en el navegador como en la línea de comando). La principal ventaja de preproyectar un archivo para utilizarlo con una proyección nula es el rendimiento .
geoidentidad
Si todo lo que necesita es escalar y centrar las entidades, puede utilizar una geoIdentidad. Esto implementa una geoTransform pero con métodos de proyección estándar como scale
, translate
y, lo más importante, fitSize
/ fitExtent
. Entonces, podemos establecer la proyección en una geoIdentidad:
var projection = d3.geoIdentity();
Actualmente, esto hace lo mismo que la proyección nula utilizada anteriormente: toma datos x,y de las geometrías geojson y los muestra como datos x,y sin transformación, tratando cada coordenada en geojson como una coordenada de píxel. Pero podemos aplicar fitSize a esto (o fitExtent), lo que escalará y traducirá automáticamente los datos al cuadro delimitador especificado:
var projection = d3.geoIdentity()
.fitSize([width,height],geojsonObject);
o
var projection = d3.geoIdentity()
.fitExtent([[left,top],[right,bottom]], geojsonObject);
Tenga en cuenta que la mayoría de los datos proyectados utilizan convenciones geográficas: y=0 está en la parte inferior, y los valores de y aumentan a medida que uno se mueve hacia el norte. En el espacio de coordenadas svg/canvas, y=0 está en la parte superior, y los valores de y aumentan a medida que uno se mueve hacia abajo. Por lo tanto, a menudo necesitaremos invertir el eje y:
var projection = d3.geoIdentity()
.fitExtent([width,height],geojsonObject)
.reflectY(true);
Este conjunto de datos en particular: https://d3js.org/us-10m.v1.json se proyectó con una proyección d3, por lo que su eje y ya se invirtió como proyecciones d3 proyectadas a un espacio de coordenadas svg o canvas.
Demostración de geoIdentidad
Mostrar fragmento de código
geotransformar
Si desea tener un poco más de control sobre cómo se muestran esos datos, puede utilizar un archivo geoTransform
.
De Mike Bostock :
¿Pero qué pasa si tu geometría ya es plana? Es decir, ¿qué sucede si solo desea tomar la geometría proyectada, pero aun así traducirla o escalarla para que se ajuste a la ventana gráfica?
Puede implementar una transformación de geometría personalizada para obtener un control total sobre el proceso de proyección.
Usar a geoTransform
es relativamente sencillo suponiendo que no desea cambiar el tipo de proyección . Por ejemplo, si desea escalar los datos, puede implementar una función corta para escalar con geoTransform
:
function scale (scaleFactor) {
return d3.geoTransform({
point: function(x, y) {
this.stream.point(x * scaleFactor, y * scaleFactor);
}
});
}
var path = d3.geoPath().projection(scale(0.2));
Sin embargo, esto ampliará todo en la esquina superior izquierda a medida que alejes el zoom. Para mantener todo centrado, puedes agregar algo de código para centrar la proyección:
function scale (scaleFactor,width,height) {
return d3.geoTransform({
point: function(x, y) {
this.stream.point( (x - width/2) * scaleFactor + width/2 , (y - height/2) * scaleFactor + height/2);
}
});
}
var path = d3.geoPath().projection(scale(0.2,width,height))
Demostración de geoTransform :
Aquí hay un ejemplo usando su archivo y una geoTransform:
Mostrar fragmento de código
Desproyectar los datos
Este método es útil en determinadas circunstancias. Pero requiere que conozca la proyección que se utilizó para crear sus datos. Usando QGIS/ArcGIS o incluso Mapshaper puedes cambiar la proyección de los datos para que se "proyecten" como WGS84 (también conocido como EPSG 4326). Una vez convertidos, tienes datos no proyectados.
En Mapshaper esto es bastante fácil con archivos de forma, arrastre los archivos .dbf, .shp y .prj de un archivo de forma a la ventana. Abra la consola en Mapshaper y escriba proj wgs84.
Si no conoce la proyección utilizada para crear los datos, no puede desproyectarla; no sabe qué transformación se aplicó y con qué parámetros.
Una vez sin proyectar, puede usar proyecciones d3 regulares tan normalmente como tenga coordenadas en el espacio de coordenadas correcto: pares de latitud y longitud.
Desproyectar es útil si también tiene datos no proyectados y desea mezclar ambos en el mismo mapa. Alternativamente, podría proyectar los datos no proyectados para que ambos utilicen el mismo sistema de coordenadas. Combinar sistemas de coordenadas incomparables en un mapa con d3 no es fácil y es probable que d3 no sea el vehículo correcto para ello. Si realmente desea replicar una proyección específica con d3 para hacer coincidir funciones que ya están proyectadas con funciones no proyectadas, entonces esta pregunta puede ser útil.
¿Cómo puedes saber si tus datos ya están proyectados?
Podrías verificar que la geometría de tus entidades respete los límites de latitud y longitud. Por ejemplo, si iniciara sesión:
d3.json("https://d3js.org/us-10m.v1.json", function (error, us){
console.log(topojson.feature(us, us.objects.states).features);
});
Verá rápidamente que los valores superan +/- 90 grados N/S y +/- 180 grados E/W. Es poco probable que sean pares largos.
Alternativamente, puede importar sus datos a un servicio en línea como mapshaper.org y compararlos con otro topojson/geojson que sepa que no está proyectado (o "proyectado" usando WGS84).
Si se trata de geojson, es posible que tenga la suerte de ver una propiedad que defina la proyección, como por ejemplo: "name": "urn:ogc:def:crs:OGC:1.3:CRS84"
(CRS significa sistema de referencia de coordenadas) o un número EPSG: EPSG:4326
(EPSG significa European Petroleum Survey Group).
Además, si sus datos se proyectan con una proyección nula pero no con una proyección estándar (escalada/alejada para garantizar que no está mirando en el área incorrecta), es posible que esté tratando con datos proyectados. Lo mismo ocurre si su ventana gráfica está completamente cubierta por una característica (y no está ampliada). Las coordenadas NaN también son un indicador potencial. Sin embargo, estos últimos indicadores de los datos proyectados también pueden significar otros problemas.
Por último, la fuente de datos también puede indicar que los datos ya están proyectados en metadatos o cómo se usan: al observar este bloque , podemos ver que no se usó ninguna proyección cuando se geoPath
definió.