D3 untuk peta --- pada tahap apa untuk membawa data ke geo?

12

Saya ingin memetakan choropleth dunia untuk tampilan dengan D3, ala:

Saya memiliki dataset yang ingin saya tampilkan yang memiliki kunci ISO-alpha-3. Begitu...

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

dll.

Mengikuti instruksi pada topojson, saya tahu saya bisa melakukan ...

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

untuk menghasilkan jmap worldmap yang diidentifikasikan oleh ISO3.

Pertanyaan saya adalah: pada titik apa dalam alur kerja saya harus menggabungkan data dari hazard.csv ke data geo? Saya sebelumnya pernah bekerja dengan qGIS sebagai GUI, tetapi di mana / haruskah / penggabungan terjadi? Di .shp? Setelah ogr2ogr? Secara dinamis di browser setelah menyusut topojson (seperti di sini http://bl.ocks.org/mbostock/4060606 http://bl.ocks.org/mbostock/3306362 )?

Saya cukup baik dengan python, tetapi cukup baru untuk javascript, dan menemukan diri saya menyalin dan menempelkan contoh Bostock lebih dari benar-benar menjadi pembuat kode generatif di sana.

(Saya juga memiliki tindak lanjut terkait, tetapi lebih banyak terlibat di Stackoverflow yang mungkin harus saya migrasi di sini: /programming/18604877/how-to-do-time-data-in-d3-maps )

Mittenchops
sumber
Saya hanya melihat contoh @ mbostock dan melihat bahwa ada satu yang secara khusus membahas GeoJoins , atau "Sebuah skrip sederhana untuk menggabungkan file GeoJSON dengan properti eksternal dalam file CSV atau TSV; diekstraksi dari TopoJSON" .
RyanKDalton

Jawaban:

11

Tanyakan kepada diri Anda dua pertanyaan:

  1. Apakah Anda akan menggunakan kembali geografi pada beberapa dataset?

    Jika Anda akan menggunakan geografi yang sama dengan beberapa dataset, maka masuk akal untuk memisahkan geografi dan data, dan bergabung dengan mereka di klien. Banyak contoh saya memiliki file CSV (atau TSV) terpisah karena alasan ini. Dengan cara ini, TopoJSON untuk negara bagian dan negara bagian AS atau juga negara dunia dapat digunakan kembali, alih-alih membuat TopoJSON yang terpisah untuk setiap contoh.

    Di sisi lain, jika Anda hanya akan menggunakan geografi ini sekali , maka Anda mungkin harus "memanggang" data ke dalam geografi sebagai properti, jika hanya untuk menyederhanakan kode. Pendekatan ini lebih sederhana karena Anda hanya perlu memuat satu file (jadi tidak ada queue.js ), dan karena data disimpan sebagai properti dari setiap fitur, Anda tidak perlu bergabung dengan data di klien (jadi tidak ada d3. peta ).

    Catatan: TSV dan CSV seringkali jauh lebih efisien dalam menyimpan properti daripada GeoJSON dan TopoJSON, hanya karena yang terakhir harus mengulangi nama properti pada setiap objek. Ukuran file bisa menjadi alasan lain untuk menyimpan data Anda dalam file terpisah dan bergabung di klien.

  2. Apakah data Anda sudah terikat pada geografi (misalnya, properti shapefile Anda)?

    Dengan asumsi Anda menjawab "tidak" untuk pertanyaan pertama dan ingin memanggang data ke dalam geografi (daripada melakukannya di klien), bagaimana Anda melakukan ini tergantung pada format data.

    Jika data Anda sudah menjadi properti dari shapefile Anda, maka gunakan topojson -puntuk mengontrol properti mana yang disimpan ke file TopoJSON yang dihasilkan. Anda juga dapat menggunakan ini untuk mengubah nama properti dan memaksa mereka ke angka, juga. Lihat Mari Membuat Peta untuk contoh.

    Jika data Anda dalam file CSV atau TSV yang terpisah, maka gunakan topojson -e (selain -p) untuk menentukan file properti eksternal yang dapat digabungkan dengan fitur geografis Anda. Cribbing contoh dari wiki, jika Anda memiliki file TSV seperti ini:

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

    Dengan menggunakan -e, Anda dapat memetakan ini ke properti output numerik bernama "pengangguran":

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

    Contoh dari pendekatan ini adalah choropleth populasi Kentucky, bl.ocks.org/5144735 .

mbostock
sumber
2
Dan di sini saya mengajukan pertanyaan pemetaan D3 saya yang sulit di stackoverflow bukannya gis.stackexchange karena saya pikir ada lebih banyak keahlian di sana --- dan kemudian master sendiri menjawab pertanyaan saya di sini. =) Nah, itu membuat 2 hal yang saya pelajari hari ini. Terima kasih!
Mittenchops
3

Pertanyaan bagus. Salah satu contoh yang Anda berikan tampaknya melakukan trik, meskipun sulit untuk diikuti.

Anda akan perhatikan bahwa contoh ini memiliki dua file data eksternal, us.json dan pengangguran.tsv . Anda dapat menganggap pengangguran.tsv seperti bahaya Anda.csv; us.json adalah fitur geografis yang Anda inginkan untuk mengaitkan parameter dari hazard.csv. Yang terakhir, pengangguran.tsv, memiliki iddan ratebidang di mana idsama dengan iddi us.json.

Di klien dengan D3 Anda harus menggabungkan data dan fitur Anda , setidaknya dengan contoh ini. Hal ini di klien bahwa tingkat pengangguran, dalam contoh ini, bergabung dengan fitur county, menggunakan d3.map () fungsi. Di sinilah diinisialisasi:

var rateById = d3.map();

Dan ini adalah tempat ratedipetakannya ke 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);

Saya harus mengakui bahwa saya tidak tahu untuk apa queue(), tetapi tidak penting untuk diskusi ini. Yang penting untuk dicatat adalah bahwa dalam idlapangan di setiap fitur county digantikan oleh pengangguran rate. yang ratesudah dapat diakses oleh identifier bersama id( EDIT: Sebagai @ blord-castillo poin, ini sebenarnya adalah generasi array baru asosiatif, atau hash kunci, di mana ratedipetakan keid ). Di sinilah ratedipanggil untuk keperluan simbologi (di sini, kelas CSS yang tersedia tersedia untuk setiap kuantil):

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

Di mana quantize()fungsi mengembalikan nama kelas CSS yang harus digunakan untuk mendesain fitur tersebut (county) berdasarkan tingkat penganggurannya, yang sekarang didefinisikan di bidang fitur id.

Arthur
sumber
antrian memungkinkan pemuatan paralel sumber data async daripada pemuatan serial.
blord-castillo
1
Apa yang terjadi dalam contoh itu adalah bahwa rateById adalah hash utama. Tidak ada perubahan yang dilakukan pada fitur negara dan data us.json tidak tersentuh. Sebaliknya, pengangguran.tsv dikonversi menjadi hash utama yang disebut 'rateById'. rateById.set () dilingkarkan di atas pengangguran.tsv sehingga kunci dimasukkan untuk setiap id di pengangguran.tsv (bukan di us.json) dan nilai kunci itu diatur ke bidang tingkat untuk id itu di pengangguran.tsv . Kemudian, rateById.get () dipanggil untuk menggunakan hash untuk mencari tingkat pengangguran oleh id; nilai itu digunakan untuk mengatur gaya pada fitur us.json, lalu dibuang.
blord-castillo
Mengapa ini / ganti / ID dengan tingkat alih-alih melampirkannya sebagai atribut di tempat lain? Ini sepertinya akan membuat lebih sulit untuk melakukan seleksi nanti.
Mittenchops
1
Itu tidak menggantikan id dengan kurs. Ini menciptakan hash pencarian dari id ke tingkat.
blord-castillo
2

Pertama, baris pertama csv Anda harus berupa daftar nama kolom yang dipisahkan koma untuk menggunakan metode ini. Jika hal ini tidak mungkin, menambahkan komentar tentang hal ini dan saya akan melihat apakah saya bisa mengetahui bagaimana menggunakan d3.csv.parseRowsbukan d3.csv.parse. d3.csv.parsedipanggil oleh fungsi penilai pada .defer(function, url, assessor).

Saya akan menganggap file Anda sekarang terlihat seperti ini:

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

Dengan ini, Anda dapat membuat hash pencarian dari ISO3 ke tingkat bahaya.

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

Panduan kode

var dangerByISO3 = d3.map();

Pertama Anda membuat objek d3.map () yang akan berfungsi sebagai hash kunci Anda, dan menyimpannya dalam variabel hazardByISO3.

queue()

Gunakan antrian untuk memuat paralel.

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

Memuat topojson Anda sebagai argumen pertama yang diteruskan ke fungsi menunggu (setelah kesalahan). Perhatikan gaya di mana fungsi ini dirantai queue(), tetapi terdaftar pada baris terpisah (tidak ada tanda titik kominasi pada queue()).

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

Dua hal terjadi di sini. Pertama, Anda memuat hazard.csv sebagai argumen kedua untuk diteruskan ke fungsi menunggu. Seperti yang akan Anda lihat di bawah, argumen ini sebenarnya tidak digunakan. Sebagai gantinya, argumen penilai disediakan untuk fungsi pemuatan, d3.csv. Penilai ini akan memproses setiap baris csv. Dalam hal ini, kami memanggil fungsi set pada hazardByISO3 sehingga untuk setiap kombinasi isokunci, kami menetapkan levelsebagai nilai untuk pergi dengan kunci itu. The +d.levelnotasi menggunakan unary +untuk memaksa nilai d.level ke angka.

.await(ready);

Setelah kedua sumber data dimuat, mereka dilewatkan sebagai dua argumen terpisah untuk fungsi ready(). Argumen pertama ke callback selalu merupakan kesalahan pertama yang terjadi. Jika tidak ada kesalahan terjadi, maka nol akan diteruskan sebagai argumen pertama. Argumen kedua adalah sumber data pertama (hasil dari tugas pertama), dan argumen ketiga adalah sumber data kedua (hasil dari tugas kedua).

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

Ini adalah fungsi panggilan balik ready(). Pertama-tama kita mengambil errorargumen yang seharusnya nol jika dua tugas pemuatan selesai dengan sukses (Anda sebenarnya harus menambahkan bahasa untuk menangkap dan menangani kesalahan). Selanjutnya kita mengambil data topojson sebagai objek countries. Data ini harus diproses dalam tubuh fungsi dengan sesuatu seperti .data(topojson.feature(world,world.objects.countries).features). Karena ready()tidak mengambil argumen ketiga, hasil dari tugas kedua, csv kami, dibuang begitu saja. Kami hanya menggunakannya untuk membangun hash kunci dan tidak membutuhkannya setelah itu.

tuan-castillo
sumber
Ya, Anda benar, csv saya benar-benar terlihat seperti csv yang terbentuk dengan baik daripada demo ceroboh yang saya posting. =) Maaf, saya akan memperbarui itu.
Mittenchops