D3 per le mappe --- in quale fase portare i dati nel geo?


12

Vorrei mappare un coropleto mondiale per la visualizzazione con D3, come segue:

Ho un set di dati che vorrei visualizzare con chiavi ISO-alpha-3. Così...

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

eccetera.

Seguendo le istruzioni su topojson, so di poter fare ...

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

per produrre un json worldmap identificato da ISO3.

La mia domanda è: a che punto del flusso di lavoro devo unire i dati di danger.csv ai dati geografici? In precedenza avevo lavorato con qGIS come GUI, ma dove / dovrebbe / dovrebbe avvenire l'unione? Nel .shp? Dopo ogr2ogr? Dinamicamente nel browser dopo la riduzione del topojson (come qui http://bl.ocks.org/mbostock/4060606 http://bl.ocks.org/mbostock/3306362 )?

Sono abbastanza bravo con Python, ma abbastanza nuovo con JavaScript, e mi ritrovo a copiare e incollare esempi di Bostock più che essere effettivamente un programmatore generativo lì.

(Ho anche un seguito correlato, ma più coinvolto su StackOverflow che forse dovrei migrare qui: /programming/18604877/how-to-do-time-data-in-d3-maps )


Stavo solo guardando gli esempi di @ mbostock e ho visto che ce n'è uno che si rivolge in modo specifico a GeoJoins , o "Un semplice script per unire un file GeoJSON con proprietà esterne in un file CSV o TSV; estratto da TopoJSON" .
RyanK Dalton,

Risposte:


11

Ponetevi due domande:

  1. Riutilizzerai la geografia su più set di dati?

    Se utilizzerai la stessa area geografica con più set di dati, ha senso mantenere separati i dati geografici e i dati e unirli nel client. Molti dei miei esempi hanno file CSV (o TSV) separati per questo motivo. In questo modo, è possibile riutilizzare il TopoJSON per stati e contee statunitensi o anche paesi del mondo , invece di creare TopoJSON separato per ogni esempio.

    D'altra parte, se utilizzerai questa geografia solo una volta , probabilmente dovresti "inserire" i dati nella geografia come proprietà, se non altro per semplificare il codice. Questo approccio è più semplice perché è necessario caricare un solo file (quindi non queue.js ) e poiché i dati sono archiviati come proprietà di ciascuna funzione, non è necessario unire i dati nel client (quindi non è necessario d3. mappa ).

    Nota a margine: TSV e CSV sono spesso molto più efficienti nella memorizzazione delle proprietà rispetto a GeoJSON e TopoJSON, semplicemente perché quest'ultimo deve ripetere i nomi delle proprietà su ogni oggetto. Le dimensioni del file possono essere un altro motivo per archiviare i tuoi dati in un file separato e unirli nel client.

  2. I tuoi dati sono già legati alla geografia (ad esempio, una proprietà del tuo file di forma)?

    Supponendo che tu abbia risposto "no" alla prima domanda e desideri inserire i dati nella geografia (piuttosto che farlo nel client), come lo fai dipende dal formato dei dati.

    Se i tuoi dati sono già una proprietà del tuo file di forma, usa topojson -pper controllare quali proprietà vengono salvate nel file TopoJSON generato. Puoi anche usarlo per rinominare le proprietà e anche forzarle in numeri. Vedi Facciamo una mappa per esempi.

    Se i tuoi dati sono in un file CSV o TSV separato, usa topojson -e (oltre a -p) per specificare un file di proprietà esterno che può essere unito alle tue caratteristiche geografiche. Paralizzando l'esempio dal wiki, se avessi un file TSV come questo:

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

    Utilizzando -e, è possibile mapparli su una proprietà di output numerica denominata "disoccupazione":

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

    Un esempio di questo approccio è il coropleto della popolazione del Kentucky, bl.ocks.org/5144735 .


2
E qui stavo facendo le mie difficili domande sulla mappatura D3 su stackoverflow invece di gis.stackexchange perché pensavo che ci fosse più esperienza lì --- e quindi il maestro stesso risponde alla mia domanda qui. =) Bene, questo rende 2 cose che ho imparato oggi. Grazie!
Mittenchops

3

Buona domanda. Uno degli esempi che hai fornito sembra fare il trucco, anche se è difficile da seguire.

Noterai che l'esempio ha due file di dati esterni, us.json e unemployment.tsv . Puoi pensare a unemployment.tsv come al tuo pericolo.csv; us.json sono le caratteristiche geografiche con le quali si desidera associare i parametri di danger.csv. Quest'ultimo, unemployment.tsv, ha ide ratecampi in cui idè uguale a idin us.json.

È nel client con D3 che dovresti unire i tuoi dati e funzionalità , almeno con questo esempio. È nel client che il tasso di disoccupazione, in questo esempio, è unito alle caratteristiche della contea, usando la funzione d3.map () . Qui è dove è inizializzato:

var rateById = d3.map();

Ed è qui che rateviene mappato su 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);

Devo ammettere che non so a cosa queue()serva, ma non è importante in questa discussione. Ciò che è importante notare è che il idcampo in ogni caratteristica della contea è sostituito dalla disoccupazione rate. the rateè ora accessibile dall'identificatore condiviso id( EDIT: Come sottolinea @ blord-castillo, questa è in realtà la generazione di un nuovo array associativo, o hash chiave, in cui rateè mappato aid ). Qui è dove rateviene richiamato ai fini della simbologia (qui, per ogni quantile sono disponibili classi CSS predefinite):

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

Dove la quantize()funzione restituisce il nome della classe CSS che dovrebbe essere usata per definire lo stile di quella caratteristica (contea) in base al suo tasso di disoccupazione, che ora è definito nel idcampo della caratteristica .



la coda consente il caricamento parallelo asincrono delle origini dati anziché il caricamento seriale.
blord-castillo,

1
Quello che sta succedendo in questo esempio è che rateById è un hash chiave. Non vengono mai apportate modifiche alle funzionalità del Paese e i dati us.json non vengono toccati. Invece, unemployment.tsv viene convertito in un hash chiave chiamato 'rateById'. rateById.set () viene ripetuto in un ciclo su unemployment.tsv in modo che venga inserita una chiave per ciascun ID in . Successivamente, rateById.get () viene chiamato per utilizzare l'hash per cercare il tasso di disoccupazione in base all'ID; quel valore viene utilizzato per impostare lo stile sulle funzioni us.json, quindi scartato.
blord-castillo,

Perché questo / sostituisce / l'ID con la tariffa invece di collegarlo come attributo da qualche altra parte? Ciò sembrerebbe rendere più difficile la selezione in un secondo momento.
Mittenchops

1
Non sostituisce l'id con la tariffa. Crea un hash di ricerca da id a rate.
blord-castillo,

2

Prima di tutto, la prima riga del tuo csv deve essere un elenco separato da virgole di nomi di colonne per utilizzare questo metodo. Se ciò non è possibile, aggiungi un commento su questo e vedrò se riesco a capire come usare d3.csv.parseRowsinvece di d3.csv.parse. d3.csv.parseviene chiamato dalla funzione di valutazione su .defer(function, url, assessor).

Presumo che il tuo file ora assomigli a questo:

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

Usando questo, puoi creare un hash di ricerca da ISO3 a livello di pericolo.

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

Procedura dettagliata del codice

var dangerByISO3 = d3.map();

Per prima cosa crei un oggetto d3.map () che funzionerà come hash della tua chiave e lo memorizzi nella variabile dangerByISO3.

queue()

Usa la coda per il caricamento parallelo.

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

Carica il tuo topojson come primo argomento da passare alla funzione wait (dopo l'errore). Nota qui lo stile in cui questa è una funzione concatenata queue(), ma elencata su una riga separata (non è presente il punto e virgola terminato queue()).

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

Qui stanno accadendo due cose. Innanzitutto, stai caricando danger.csv come secondo argomento da passare alla funzione waitit. Come vedrai di seguito, questo argomento non è effettivamente utilizzato. Invece, viene fornito un argomento del valutatore alla funzione di caricamento, d3.csv. Questo valutatore elaborerà ogni riga del CSV. In questo caso, chiamiamo la funzione set su dangerByISO3 in modo che per ogni combinazione di una isochiave, impostiamo levelcome valore da utilizzare con quella chiave. La +d.levelnotazione usa unario +per forzare il valore di d.level su un numero.

.await(ready);

Una volta caricate entrambe le origini dati, vengono passate come due argomenti separati alla funzione ready(). Il primo argomento del callback è sempre il primo errore che si è verificato. Se non si è verificato alcun errore, il primo argomento verrà passato a null. Il secondo argomento è la prima origine dati (risultato della prima attività) e il terzo argomento è la seconda origine dati (risultato della seconda attività).

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

Questa è la funzione di richiamata ready(). Innanzitutto prendiamo l' errorargomento che dovrebbe essere nullo se le due attività di caricamento sono state completate correttamente (dovresti effettivamente aggiungere la lingua per rilevare e gestire gli errori). Successivamente prendiamo i dati topojson come oggetto countries. Questi dati dovrebbero essere elaborati nel corpo della funzione con qualcosa di simile .data(topojson.feature(world,world.objects.countries).features). Poiché ready()non accetta un terzo argomento, il risultato del secondo compito, il nostro CSV, viene semplicemente scartato. L'abbiamo usato solo per creare l'hash chiave e non ne abbiamo avuto bisogno dopo.


Sì, hai ragione, il mio CSV sembra davvero un CSV ben formato invece della demo trascurata che ho pubblicato. =) Mi dispiace, lo aggiornerò.
Mittenchops
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.