D3 pour les cartes --- à quelle étape apporter des données à la géo?

12

Je voudrais cartographier un choroplèthe du monde à afficher avec D3, à la:

J'ai un ensemble de données que j'aimerais afficher et qui correspond aux clés ISO-alpha-3. Donc...

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

etc.

En suivant les instructions sur topojson, je sais que je peux le faire ...

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

pour produire une carte du monde json identifiée par ISO3.

Ma question est: à quel stade du workflow dois-je fusionner les données de danger.csv avec les données géographiques? J'avais précédemment travaillé avec qGIS en tant qu'interface graphique, mais où / devrait / la fusion se produire? Dans le .shp? Après l'ogr2ogr? Dynamiquement dans le navigateur après la réduction de topojson (comme ici http://bl.ocks.org/mbostock/4060606 http://bl.ocks.org/mbostock/3306362 )?

Je suis assez bon avec python, mais assez nouveau pour javascript, et je me retrouve à copier et à coller des exemples Bostock plus qu'à être un codeur génératif là-bas.

(J'ai également un suivi connexe, mais plus impliqué sur Stackoverflow que je devrais peut-être migrer ici: /programming/18604877/how-to-do-time-data-in-d3-maps )

Mittenchops
la source
Je regardais juste les exemples de @ mbostock et j'ai vu qu'il y en a un qui traite spécifiquement les GeoJoins , ou "Un simple script pour joindre un fichier GeoJSON avec des propriétés externes dans un fichier CSV ou TSV; extrait de TopoJSON" .
RyanKDalton

Réponses:

11

Posez-vous deux questions:

  1. Allez-vous réutiliser la géographie sur plusieurs jeux de données?

    Si vous utilisez la même géographie avec plusieurs jeux de données, il est logique de conserver la géographie et les données séparées et de les joindre au client. Beaucoup de mes exemples ont des fichiers CSV (ou TSV) séparés pour cette raison. De cette façon, le TopoJSON pour les États et les comtés américains ou les pays du monde peut également être réutilisé, au lieu de créer un TopoJSON distinct pour chaque exemple.

    D'un autre côté, si vous n'utilisez cette géographie qu'une seule fois , vous devriez probablement «cuire» les données dans la géographie en tant que propriétés, ne serait-ce que pour simplifier le code. Cette approche est plus simple car vous n'avez besoin que de charger un seul fichier (donc pas de queue.js ), et comme les données sont stockées en tant que propriétés de chaque fonctionnalité, vous n'avez pas besoin de joindre les données dans le client (donc pas de d3. carte ).

    Remarque: TSV et CSV sont souvent beaucoup plus efficaces pour stocker des propriétés que GeoJSON et TopoJSON, simplement parce que ces derniers doivent répéter les noms de propriété sur chaque objet. La taille du fichier peut être une autre raison de stocker vos données dans un fichier séparé et de les joindre au client.

  2. Vos données sont-elles déjà liées à la géographie (par exemple, une propriété de votre fichier de formes)?

    En supposant que vous avez répondu «non» à la première question et que vous souhaitez intégrer les données dans la géographie (plutôt que de le faire dans le client), la façon dont vous procédez dépend du format des données.

    Si vos données sont déjà une propriété de votre fichier de formes, utilisez-les topojson -ppour contrôler les propriétés enregistrées dans le fichier TopoJSON généré. Vous pouvez également l'utiliser pour renommer des propriétés et les contraindre à des nombres également. Voir Créons une carte pour des exemples.

    Si vos données se trouvent dans un fichier CSV ou TSV distinct, utilisez topojson -e (en plus de -p) pour spécifier un fichier de propriétés externe qui peut être joint à vos entités géographiques. Cribting l'exemple du wiki, si vous aviez un fichier TSV comme celui-ci:

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

    À l'aide de -e, vous pouvez les mapper à une propriété de sortie numérique nommée «chômage»:

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

    Un exemple de cette approche est le choroplèth de la population du Kentucky, bl.ocks.org/5144735 .

mbostock
la source
2
Et ici, je posais mes questions difficiles sur le mappage D3 sur stackoverflow au lieu de gis.stackexchange parce que je pensais qu'il y avait plus d'expertise là-bas - puis le maître lui-même répond à ma question ici. =) Eh bien, cela fait 2 choses que j'ai apprises aujourd'hui. Merci!
Mittenchops
3

Bonne question. L'un des exemples que vous avez fournis semble faire l'affaire, bien qu'il soit difficile à suivre.

Vous remarquerez que l'exemple a deux fichiers de données externes, us.json et chômeurs.tsv . Vous pouvez penser au chomage.tsv comme à votre danger.csv; us.json sont les entités géographiques auxquelles vous souhaitez associer des paramètres de danger.csv. Ce dernier, chomage.tsv, a idet des ratechamps où le idest le même que iddans us.json.

C'est dans le client avec D3 que vous devez fusionner vos données et fonctionnalités , au moins par cet exemple. C'est dans le client que le taux de chômage, dans cet exemple, est joint aux caractéristiques du comté, à l'aide de la fonction d3.map () . C'est là qu'il est initialisé:

var rateById = d3.map();

Et c'est là que ratese mappe le 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);

Je dois admettre que je ne sais pas à quoi queue()ça sert, mais ce n'est pas important pour cette discussion. Ce qui est important à noter, c'est que le idchamp de chaque caractéristique du comté est remplacé par le chômage rate. le rateest désormais accessible par l'identifiant partagé id( EDIT: comme le souligne @ blord-castillo, il s'agit en fait de la génération d'un nouveau tableau associatif, ou hachage de clé, où le rateest mappé auid ). C'est là que le rateest appelé à des fins de symbologie (ici, des classes CSS prédéfinies sont disponibles pour chaque quantile):

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

Où la quantize()fonction renvoie le nom de la classe CSS qui doit être utilisée pour styliser cette fonction (comté) en fonction de son taux de chômage, qui est désormais défini dans le idchamp de la fonction .

Arthur
la source
1
Pour info sur la file d'attente: bsumm.net/2013/03/31/analyzing-mbostocks-queue-js.html
johanvdw
queue permet le chargement parallèle asynchrone des sources de données au lieu du chargement en série.
blord-castillo
1
Ce qui se passe dans cet exemple, c'est que rateById est un hachage de clé. Aucune modification n'est jamais apportée aux fonctionnalités du pays et les données us.json sont intactes. Au lieu de cela, chomage.tsv est converti en un hachage clé appelé «rateById». rateById.set () est bouclé sur chômage.tsv afin qu'une clé soit insérée pour chaque identifiant dans chômage.tsv (pas dans us.json) et que la valeur de cette clé soit définie dans le champ de taux pour cet identifiant dans chômage.tsv . Plus tard, rateById.get () est appelé pour utiliser le hachage pour rechercher le taux de chômage par id; cette valeur est utilisée pour définir le style sur les fonctionnalités us.json, puis supprimée.
blord-castillo
Pourquoi est-ce que / remplace / l'ID par le taux au lieu de le joindre comme attribut ailleurs? Cela semble rendre plus difficile la sélection ultérieure.
Mittenchops
1
Il ne remplace pas l'id par le taux. Il crée un hachage de recherche de l'ID au taux.
blord-castillo
2

Tout d'abord, la première ligne de votre csv doit être une liste de noms de colonnes séparés par des virgules pour utiliser cette méthode. Si ce n'est pas possible, ajoutez un commentaire à ce sujet et je verrai si je peux trouver comment utiliser à la d3.csv.parseRowsplace de d3.csv.parse. d3.csv.parseest appelé par la fonction d'évaluateur sur .defer(function, url, assessor).

Je vais supposer que votre fichier ressemble maintenant à ceci:

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

En utilisant cela, vous pouvez créer un hachage de recherche de ISO3 au niveau de danger.

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

Procédure pas à pas de code

var dangerByISO3 = d3.map();

Vous créez d'abord un objet d3.map () qui fonctionnera comme votre hachage de clé et le stockez dans la variable dangerByISO3.

queue()

Utilisez la file d'attente pour le chargement parallèle.

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

Chargez votre topojson comme premier argument à passer à la fonction wait (après erreur). Notez le style ici où il s'agit d'une fonction chaînée activée queue(), mais répertoriée sur une ligne distincte (il n'y a pas de point-virgule de fin activé queue()).

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

Il se passe deux choses ici. Tout d'abord, vous chargez danger.csv comme deuxième argument à passer à la fonction wait. Comme vous le verrez ci-dessous, cet argument n'est pas réellement utilisé. Au lieu de cela, un argument assesseur est fourni à la fonction de chargement, d3.csv. Cet évaluateur traitera chaque ligne du csv. Dans ce cas, nous appelons la fonction set sur dangerByISO3 de sorte que pour chaque combinaison d'une isoclé, nous définissons levella valeur comme aller avec cette clé. La +d.levelnotation utilise unaire +pour contraindre la valeur de d.level à un nombre.

.await(ready);

Une fois les deux sources de données chargées, elles sont transmises en tant que deux arguments distincts à la fonction ready(). Le premier argument du rappel est toujours la première erreur qui s'est produite. Si aucune erreur ne s'est produite, alors null sera transmis comme premier argument. Le deuxième argument est la première source de données (résultat de la première tâche) et le troisième argument est la deuxième source de données (résultat de la deuxième tâche).

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

Il s'agit de la fonction de rappel ready(). Nous prenons d'abord l' errorargument qui devrait être nul si les deux tâches de chargement se sont terminées avec succès (vous devez en fait ajouter un langage pour détecter et gérer les erreurs). Ensuite, nous prenons les données topojson comme objet countries. Ces données doivent être traitées dans le corps de la fonction avec quelque chose comme .data(topojson.feature(world,world.objects.countries).features). Puisque ready()ne prend pas un troisième argument, le résultat de la deuxième tâche, notre csv, est simplement rejeté. Nous l'avons utilisé uniquement pour créer le hachage de clé et n'en avons plus eu besoin par la suite.

blord-castillo
la source
Ouais, tu as raison, mon csv ressemble en fait à un csv bien formé au lieu de la démo négligente que j'ai postée. =) Désolé, je vais mettre à jour cela.
Mittenchops du