D3 para mapas --- ¿en qué etapa llevar datos a la geografía?

12

Me gustaría mapear un coroplés mundial para mostrar con D3, a la:

Tengo un conjunto de datos que me gustaría mostrar que está codificado para las teclas ISO-alpha-3. Entonces...

danger.csv
iso,level
AFG,100
ALB,0
DZA,12

etc.

Siguiendo las instrucciones sobre topojson, sé que puedo hacer ...

wget "http://www.naturalearthdata.com/http//www.naturalearthdata.com/download/50m/cultural/ne_50m_admin_0_countries.zip"
unzip ne_50m_admin_0_countries.zip
ogr2ogr -f "GeoJSON" output_features.json ne_50m_admin_0_countries.shp -select iso_a3
topojson -o topo.json output_features.json --id-property iso_a3

para producir un mapa mundial json que está identificado por ISO3.

Mi pregunta es: ¿en qué punto del flujo de trabajo debo fusionar los datos de danger.csv en los datos geográficos? Anteriormente había trabajado con qGIS como GUI, pero ¿dónde / debería / ocurrir la fusión? En el .shp? ¿Después del ogr2ogr? ¿Dinámicamente en el navegador después del encogimiento topojson (como aquí http://bl.ocks.org/mbostock/4060606 http://bl.ocks.org/mbostock/3306362 )?

Soy bastante bueno con Python, pero bastante nuevo en JavaScript, y me encuentro copiando y pegando ejemplos de Bostock más que ser un codificador generativo allí.

(También tengo un seguimiento relacionado, pero más complicado, en Stackoverflow que tal vez debería migrar aquí: /programming/18604877/how-to-do-time-data-in-d3-maps )

Mittenchops
fuente
Estaba mirando los ejemplos de @ mbostock y vi que hay uno que se dirige específicamente a GeoJoins , o "Un script simple para unir un archivo GeoJSON con propiedades externas en un archivo CSV o TSV; extraído de TopoJSON" .
RyanKDalton

Respuestas:

11

Hágase dos preguntas:

  1. ¿Vas a reutilizar la geografía en múltiples conjuntos de datos?

    Si usará la misma geografía con múltiples conjuntos de datos, entonces tiene sentido mantener la geografía y los datos separados, y unirlos en el cliente. Muchos de mis ejemplos tienen archivos CSV (o TSV) separados por este motivo. De esta manera, el TopoJSON para los estados y condados de EE. UU. O los países del mundo también se pueden reutilizar, en lugar de crear TopoJSON por separado para cada ejemplo.

    Por otro lado, si solo usará esta geografía una vez , entonces probablemente debería "hornear" los datos en la geografía como propiedades, aunque solo sea para simplificar el código. Este enfoque es más simple porque solo necesita cargar un solo archivo (por lo que no hay queue.js ), y dado que los datos se almacenan como propiedades de cada característica, no necesita unir los datos en el cliente (por lo tanto, no d3. mapa )

    Nota al margen: TSV y CSV a menudo son mucho más eficientes para almacenar propiedades que GeoJSON y TopoJSON, simplemente porque este último debe repetir los nombres de propiedad en cada objeto. El tamaño del archivo puede ser otra razón para almacenar sus datos en un archivo separado y unirlos en el cliente.

  2. ¿Sus datos ya están vinculados a la geografía (por ejemplo, una propiedad de su shapefile)?

    Suponiendo que respondió "no" a la primera pregunta y desea incorporar los datos a la geografía (en lugar de hacerlo en el cliente), la forma de hacerlo depende del formato de los datos.

    Si sus datos ya son una propiedad de su shapefile, use topojson -ppara controlar qué propiedades se guardan en el archivo TopoJSON generado. También puede usar esto para cambiar el nombre de las propiedades y obligarlas a números, también. Vea Hagamos un mapa para ver ejemplos.

    Si sus datos están en un archivo CSV o TSV separado, use topojson -e (además de -p) para especificar un archivo de propiedades externo que se pueda unir a sus características geográficas. Cribando el ejemplo de la wiki, si tuviera un archivo TSV como este:

    FIPS    rate
    1001    .097
    1003    .091
    1005    .134
    1007    .121
    1009    .099
    1011    .164
    1013    .167
    1015    .108
    1017    .186
    1019    .118
    1021    .099

    Utilizando -e, puede asignar estos a una propiedad de salida numérica llamada "desempleo":

    topojson \
      -o output.json \
      -e unemployment.tsv \
      --id-property=+FIPS \
      -p unemployment=+rate \
      -- input.shp

    Un ejemplo de este enfoque es el choropleth de la población de Kentucky, bl.ocks.org/5144735 .

mbostock
fuente
2
Y aquí estaba haciendo mis preguntas difíciles de mapeo D3 en stackoverflow en lugar de gis.stackexchange porque pensé que había más experiencia allí --- y luego el maestro mismo responde mi pregunta aquí. =) Bueno, eso hace 2 cosas que aprendí hoy. ¡Gracias!
Mittenchops
3

Buena pregunta. Uno de los ejemplos que proporcionó parece hacer el truco, aunque es difícil de seguir.

Notará que el ejemplo tiene dos archivos de datos externos, us.json y desempleo.tsv . Puedes pensar en paro.tsv como si fuera tu peligro.csv; us.json son las características geográficas con las que desea asociar parámetros de danger.csv. El último, paro.tsv, tiene idy ratecampos donde el ides el mismo que iden us.json.

Es en el cliente con D3 que debe fusionar sus datos y características , al menos en este ejemplo. Es en el cliente que la tasa de desempleo, en este ejemplo, se une a las características del condado, utilizando la función d3.map () . Aquí es donde se inicializa:

var rateById = d3.map();

Y aquí es donde ratese asigna a id:

queue()
    .defer(d3.json, "/mbostock/raw/4090846/us.json")
    .defer(d3.tsv, "unemployment.tsv", function(d) { rateById.set(d.id, +d.rate); })
    .await(ready);

Debo admitir que no sé para qué queue()sirve, pero no es importante para esta discusión. Es importante tener en cuenta que el idcampo en cada característica del condado se reemplaza por el desempleo rate. el ratees ahora accesible por el identificador común id( EDIT: Como @ blord-Castillo señala, esto es en realidad la generación de una nueva matriz asociativa, o hash de clave, donde el ratese asigna alid ). Aquí es donde ratese llama a los fines de simbología (aquí, las clases CSS predefinidas están disponibles para cada cuantil):

...
.enter().append("path")
  .attr("class", function(d) { return quantize(rateById.get(d.id)); })
  .attr("d", path);

Donde la quantize()función devuelve el nombre de la clase CSS que debe usarse para diseñar esa característica (condado) en función de su tasa de desempleo, que ahora se define en el idcampo de la característica .

Arturo
fuente
la cola permite la carga paralela asíncrona de las fuentes de datos en lugar de la carga en serie.
blord-castillo
1
Lo que sucede en ese ejemplo es que rateById es un hash clave. Nunca se realizan cambios en las características del país y los datos de us.json no se han modificado. En su lugar, desempleo.tsv se convierte en un hash clave llamado 'rateById'. rateById.set () se enlaza sobre desempleo.tsv para que se inserte una clave para cada identificación en desempleo.tsv (no en us.json) y el valor de esa clave se establece en el campo de tasa para esa identificación en desempleo.tsv . Más adelante, se llama a rateById.get () para usar el hash para buscar la tasa de desempleo por id; ese valor se usa para establecer el estilo en las características us.json, luego se descarta.
blord-castillo
¿Por qué esto / reemplaza / el ID con la tasa en lugar de adjuntarlo como un atributo en otro lugar? Esto parece dificultar la selección posterior.
Mittenchops
1
No reemplaza la identificación con la tasa. Crea un hash de búsqueda de id para calificar.
blord-castillo
2

En primer lugar, la primera fila de su csv debe ser una lista separada por comas de nombres de columna para usar este método. Si esto no es posible, agregue un comentario al respecto y veré si puedo averiguar cómo usarlo en d3.csv.parseRowslugar de hacerlo d3.csv.parse. d3.csv.parsees llamado por la función de evaluador en .defer(function, url, assessor).

Voy a asumir que su archivo ahora se ve así:

danger.csv
iso,level
AFG,100
ALB,0
DZA,12
...

Con esto, puede crear un hash de búsqueda desde ISO3 hasta el nivel de peligro.

var dangerByISO3 = d3.map();
queue()
    .defer(d3.json, "url to topo.json")
    .defer(d3.csv, "url to danger.csv", function(d) {dangerByISO3.set(d.iso, +d.level);})
    .await(ready);
function ready(error, world) {
    //You now have world as your available topojson
    //And you have dangerByISO3 as your danger level hash
    //You can lookup a danger level by dangerByISO3.get(ISO3 code)
}

Tutorial de código

var dangerByISO3 = d3.map();

Primero crea un objeto d3.map () que funcionará como el hash de su clave y lo almacena en la variable dangerByISO3.

queue()

Utilice la cola para carga paralela.

.defer(d3.json, "url to topo.json")

Cargue su topojson como el primer argumento que se pasará a la función de espera (después del error). Tenga en cuenta el estilo aquí donde esta es una función encadenada queue(), pero aparece en una línea separada (no hay punto y coma finalizado queue()).

.defer(d3.csv, "url to danger.csv", function(d) {dangerByISO3.set(d.iso, +d.level);})

Dos cosas están sucediendo aquí. Primero, está cargando danger.csv como su segundo argumento para pasar a la función de espera. Como verá a continuación, este argumento no se usa realmente. En cambio, se proporciona un argumento evaluador para la función de carga, d3.csv. Este asesor procesará cada fila del csv. En este caso, llamamos a la función set en dangerByISO3 para que, para cada combinación de una isotecla, establezcamos levelel valor como el que va con esa tecla. La +d.levelnotación usa unario +para coaccionar el valor de d.level a un número.

.await(ready);

Una vez que se cargan ambas fuentes de datos, se pasan como dos argumentos separados a la función ready(). El primer argumento para la devolución de llamada es siempre el primer error que ocurrió. Si no se produjo ningún error, se pasará nulo como primer argumento. El segundo argumento es la primera fuente de datos (resultado de la primera tarea), y el tercer argumento es la segunda fuente de datos (resultado de la segunda tarea).

function ready(error, world) {...}

Esta es la función de devolución de llamada ready(). Primero tomamos el errorargumento que debería ser nulo si las dos tareas de carga se completan con éxito (en realidad, debe agregar un idioma para detectar y manejar los errores). A continuación tomamos los datos de topojson como el objeto countries. Estos datos deben procesarse en el cuerpo de la función con algo como .data(topojson.feature(world,world.objects.countries).features). Como ready()no toma un tercer argumento, el resultado de la segunda tarea, nuestro csv, simplemente se descarta. Solo lo usamos para construir el hash clave y no lo necesitamos después de eso.

castillo-blord
fuente
Sí, tienes razón, mi csv realmente se ve como un csv bien formado en lugar de la demo descuidada que publiqué. =) Lo siento, lo actualizaré.
Mittenchops