D3 dla map --- na jakim etapie należy wprowadzić dane do geo?

12

Chciałbym zmapować choropletę świata do wyświetlenia za pomocą D3, a la:

Mam zestaw danych, który chciałbym wyświetlić, który jest przypisany do kluczy ISO-alpha-3. Więc...

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

itp.

Postępując zgodnie z instrukcjami dotyczącymi topojson, wiem, że mogę to zrobić ...

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

aby stworzyć mapę świata Json, która jest identyfikowana przez ISO3.

Moje pytanie brzmi: w którym momencie w przepływie pracy powinienem scalić dane z hazard.csv z danymi geograficznymi? Wcześniej pracowałam z qGIS jako GUI, ale gdzie / powinna / nastąpić scalenie? W .shp? Po ogr2ogr? Dynamicznie w przeglądarce po zmniejszeniu topojson (jak tutaj http://bl.ocks.org/mbostock/4060606 http://bl.ocks.org/mbostock/3306362 )?

Jestem całkiem dobry w Pythonie, ale całkiem nowy w javascript i odkrywam, że kopiuję i wklejam przykłady Bostocka bardziej niż faktyczny koder.

(Mam również powiązane, ale bardziej zaangażowane działania następcze dotyczące Stackoverflow, które może powinienem przeprowadzić tutaj: /programming/18604877/how-to-do-time-data-in-d3-maps )

Mittenchops
źródło
Właśnie patrzyłem na przykłady @ mbostock i zobaczyłem, że jest taki, który konkretnie dotyczy GeoJoins , lub „Prosty skrypt do dołączania pliku GeoJSON z zewnętrznymi właściwościami w pliku CSV lub TSV; wyodrębniony z TopoJSON” .
RyanKDalton

Odpowiedzi:

11

Zadaj sobie dwa pytania:

  1. Czy zamierzasz ponownie wykorzystać geografię w wielu zestawach danych?

    Jeśli użyjesz tej samej lokalizacji geograficznej z wieloma zestawami danych, warto oddzielić geografię od danych i połączyć je w kliencie. Z tego powodu wiele moich przykładów ma osobne pliki CSV (lub TSV). W ten sposób TopoJSON dla stanów USA i hrabstw lub podobnie krajów świata może zostać ponownie wykorzystany, zamiast tworzenia osobnego TopoJSON dla każdego przykładu.

    Z drugiej strony, jeśli użyjesz tej geografii tylko raz , prawdopodobnie powinieneś „upiec” dane w geografii jako właściwości, choćby po to, aby uprościć kod. To podejście jest prostsze, ponieważ wystarczy załadować tylko jeden plik (więc bez kolejki.js ), a ponieważ dane są przechowywane jako właściwości każdej funkcji, nie trzeba dołączać danych w kliencie (więc nie d3. mapa ).

    Uwaga dodatkowa: TSV i CSV są często znacznie wydajniejsze w przechowywaniu właściwości niż GeoJSON i TopoJSON, po prostu dlatego, że ten ostatni musi powtarzać nazwy właściwości na każdym obiekcie. Rozmiar pliku może być kolejnym powodem przechowywania danych w osobnym pliku i dołączenia go do klienta.

  2. Czy Twoje dane są już powiązane z lokalizacją geograficzną (np. Właściwością pliku kształtu)?

    Zakładając, że odpowiedziałeś „nie” na pierwsze pytanie i chcesz upiec dane w geografii (zamiast robić to w kliencie), to, jak to zrobisz, zależy od formatu danych.

    Jeśli Twoje dane są już własnością topojson -ppliku shapefile , użyj przycisku, aby kontrolować, które właściwości są zapisywane w generowanym pliku TopoJSON. Możesz także użyć tego do zmiany nazw właściwości i przymuszenia ich do liczb. Zobacz przykłady Stwórzmy mapę .

    Jeśli dane znajdują się w osobnym pliku CSV lub TSV, użyj topojson -e (oprócz -p), aby określić zewnętrzny plik właściwości , który można połączyć z funkcjami geograficznymi. Skopiowanie przykładu z wiki, jeśli masz plik TSV taki jak ten:

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

    Za pomocą -emożna mapować je na właściwość danych liczbowych o nazwie „bezrobocie”:

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

    Przykładem takiego podejścia jest choropleth populacji Kentucky, bl.ocks.org/5144735 .

mbostock
źródło
2
I tutaj zadawałem moje trudne pytania dotyczące mapowania D3 przy przepływie stosu zamiast gis.stackexchange, ponieważ myślałem, że jest tam więcej wiedzy - a wtedy sam mistrz odpowiada na moje pytanie tutaj. =) Cóż, to czyni dwie rzeczy, których nauczyłem się dzisiaj. Dzięki!
Mittenchops,
3

Dobre pytanie. Wydaje się, że jeden z podanych przez ciebie przykładów załatwił sprawę, choć trudno jest naśladować.

Zauważysz, że przykład ma dwa zewnętrzne pliki danych, us.json i bezrobocia.tsv . Możesz myśleć o bezrobocie.tsv jak o niebezpieczeństwie.csv; us.json to cechy geograficzne, z którymi chcesz powiązać parametry z niebezpieczeństwa.csv. Ta ostatnia, bezrobocie.tsv, ma pola idi ratepola, w których idjest taka sama jak idw us.json.

To w kliencie z wersją D3 powinieneś połączyć swoje dane i funkcje , przynajmniej w tym przykładzie. To w kliencie stopa bezrobocia, w tym przykładzie, jest połączona z funkcjami hrabstwa za pomocą funkcji d3.map () . Tutaj jest inicjowany:

var rateById = d3.map();

I tutaj ratemapuje się na 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);

Muszę przyznać, że nie wiem po co queue(), ale nie jest to ważne w tej dyskusji. Co jest ważne, aby pamiętać, że pola w każdej funkcji powiatu otrzymuje bezrobocia . jest teraz dostępny przez udostępnionego identyfikatora ( EDIT: Jak @ blord-Castillo wskazuje jest to rzeczywiście pokolenie nowej tablicy asocjacyjnej, lub klawisz krzyżyka, gdzie jest mapowany do ). Tutaj wywoływane jest to dla celów symboliki (tutaj predefiniowane klasy CSS są dostępne dla każdego kwantyla):idraterateidrateidrate

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

Gdzie quantize()funkcja zwraca nazwę klasy CSS, której należy użyć do stylizacji tej cechy (hrabstwa) na podstawie jej stopy bezrobocia, która jest teraz zdefiniowana w polu cechy id.

Artur
źródło
1
Do Twojej wiadomości w kolejce: bsumm.net/2013/03/31/analyzing-mbostocks-queue-js.html
johanvdw
kolejka umożliwia asynchroniczne równoległe ładowanie źródeł danych zamiast ładowania szeregowego.
blord-castillo
1
W tym przykładzie dzieje się tak, że rateById jest kluczowym hashem. Nigdy nie wprowadzono żadnych zmian do funkcji kraju, a dane us.json pozostają nietknięte. Zamiast tego plik bezrobocia.tsv jest konwertowany na kluczowy skrót o nazwie „rateById”. rateById.set () jest zapętlony nad bezrobociem.tsv, dzięki czemu klucz jest wstawiany dla każdego identyfikatora w bezrobocia.tsv (nie w us.json), a wartość tego klucza jest ustawiana w polu stawki dla tego identyfikatora w bezrobocie.tsv . Później wywołana jest rateById.get (), aby użyć skrótu do sprawdzenia stopy bezrobocia według identyfikatora; ta wartość służy do ustawienia stylu dla funkcji us.json, a następnie jest odrzucana.
blord-castillo
Dlaczego ten / zamienia / identyfikator na stawkę zamiast przypisywać go jako atrybut gdzie indziej? Wydaje się, że utrudnia to później wybór.
Mittenchops,
1
Nie zastępuje identyfikatora stopą. Tworzy skrót mieszania od identyfikatora do stawki.
blord-castillo
2

Po pierwsze, pierwszy wiersz pliku csv musi być oddzieloną przecinkami listą nazw kolumn, aby można było użyć tej metody. Jeśli nie jest to możliwe, dodaj komentarz na ten temat, a zobaczę, czy mogę wymyślić, jak go używać d3.csv.parseRowszamiast d3.csv.parse. d3.csv.parsejest wywoływana przez funkcję asesora na .defer(function, url, assessor).

Zakładam, że twój plik wygląda teraz tak:

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

Korzystając z tego, możesz utworzyć skrót kontrolny od ISO3 do poziomu niebezpieczeństwa.

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

Przewodnik po kodzie

var dangerByISO3 = d3.map();

Najpierw utworzysz obiekt d3.map (), który będzie działał jako skrót skrótu, i zapiszesz go w zmiennej niebezpieczeństwoByISO3.

queue()

Użyj kolejki do równoległego ładowania.

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

Załaduj swój topojson jako pierwszy argument, który zostanie przekazany do funkcji oczekiwania (po błędzie). Zwróć uwagę na styl, w którym jest to funkcja łańcuchowa queue(), ale wymieniona w osobnej linii (nie ma kończącego średnika queue()).

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

Działają się tutaj dwie rzeczy. Po pierwsze, ładujesz niebezpieczeństwo.csv jako drugi argument, który zostanie przekazany do funkcji oczekiwania. Jak zobaczysz poniżej, ten argument nie jest faktycznie używany. Zamiast tego do funkcji ładującej d3.csv dostarczany jest argument oceniający. Ten asesor przetworzy każdy wiersz pliku csv. W takim przypadku wywołujemy funkcję set w niebezpieczeństwieBISISO3, aby dla każdej kombinacji isoklucza ustawiać wartość leveljako wartość odpowiadającą temu kluczowi. +d.levelNotacja wykorzystuje jednoskładnikowa +zmusić wartość d.level do liczby.

.await(ready);

Po załadowaniu obu źródeł danych są one przekazywane do funkcji jako dwa osobne argumenty ready(). Pierwszym argumentem wywołania zwrotnego jest zawsze pierwszy występujący błąd. Jeśli nie wystąpił błąd, jako pierwszy argument zostanie przekazany null. Drugi argument to pierwsze źródło danych (wynik pierwszego zadania), a trzeci argument to drugie źródło danych (wynik drugiego zadania).

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

To jest funkcja oddzwaniania ready(). Najpierw bierzemy errorargument, który powinien być zerowy, jeśli dwa zadania ładowania zakończą się powodzeniem (powinieneś dodać język, aby wychwytywać i obsługiwać błędy). Następnie bierzemy dane topojson jako obiekt countries. Dane te powinny być przetwarzane w treści funkcji za pomocą czegoś podobnego .data(topojson.feature(world,world.objects.countries).features). Ponieważ ready()nie bierze trzeciego argumentu, wynik drugiego zadania, nasze csv, jest po prostu odrzucany. Użyliśmy go tylko do zbudowania kluczowego skrótu i ​​nie potrzebowaliśmy go później.

blord-castillo
źródło
Tak, masz rację, moje csv faktycznie wygląda jak dobrze sformatowany csv zamiast nieostrożnego dema, które opublikowałem. =) Przepraszam, zaktualizuję to.
Mittenchops