D3 para mapas --- em que estágio trazer dados para a região geográfica?

12

Eu gostaria de mapear um coro mundial para exibição com D3, a la:

Eu tenho um conjunto de dados que eu gostaria de exibir que é codificado para chaves ISO-alfa-3. Então...

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

etc.

Seguindo as instruções no topojson, eu sei que posso fazer ...

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 produzir um json de mapa do mundo que seja identificado pela ISO3.

Minha pergunta é: em que momento do fluxo de trabalho devo mesclar os dados de danger.csv nos dados geográficos? Eu já havia trabalhado com o qGIS como uma GUI, mas onde / deve / a fusão acontecer? No .shp? Após o ogr2ogr? Dinamicamente no navegador após o topojson encolher (como aqui http://bl.ocks.org/mbostock/4060606 http://bl.ocks.org/mbostock/3306362 )?

Eu sou muito bom com python, mas muito novo em javascript, e me pego copiando e colando exemplos de Bostock mais do que realmente ser um codificador generativo lá.

(Eu também tenho um acompanhamento relacionado, mas mais envolvido, ao Stackoverflow, que talvez eu deva migrar aqui: /programming/18604877/how-to-do-time-data-in-d3-maps )

Mittenchops
fonte
Eu estava apenas olhando os exemplos do @ mbostock e vi que existe um que aborda especificamente o GeoJoins , ou "Um script simples para ingressar em um arquivo GeoJSON com propriedades externas em um arquivo CSV ou TSV; extraído do TopoJSON" .
RyanKDalton

Respostas:

11

Faça a si mesmo duas perguntas:

  1. Você vai reutilizar a geografia em vários conjuntos de dados?

    Se você usar a mesma região com vários conjuntos de dados, faz sentido manter a região e os dados separados e juntá-los ao cliente. Muitos dos meus exemplos têm arquivos CSV (ou TSV) separados por esse motivo. Dessa forma, o TopoJSON para estados e condados dos EUA ou países do mesmo modo pode ser reutilizado, em vez de criar o TopoJSON separado para cada exemplo.

    Por outro lado, se você usar essa geografia apenas uma vez , provavelmente deverá “inserir” os dados na geografia como propriedades, apenas para simplificar o código. Essa abordagem é mais simples porque você só precisa carregar um único arquivo (portanto, sem queue.js ) e, como os dados são armazenados como propriedades de cada recurso, não é necessário associá-los ao cliente (portanto, não d3. mapa ).

    Nota lateral: TSV e CSV geralmente são muito mais eficientes no armazenamento de propriedades do que GeoJSON e TopoJSON, simplesmente porque o último deve repetir os nomes de propriedades em todos os objetos. O tamanho do arquivo pode ser outro motivo para armazenar seus dados em um arquivo separado e ingressá-lo no cliente.

  2. Seus dados já estão vinculados à geografia (por exemplo, uma propriedade do seu shapefile)?

    Supondo que você respondeu "não" à primeira pergunta e deseja incluir os dados na geografia (em vez de fazê-lo no cliente), como você faz isso depende do formato dos dados.

    Se seus dados já são propriedade do seu shapefile, use topojson -ppara controlar quais propriedades são salvas no arquivo TopoJSON gerado. Você também pode usar isso para renomear propriedades e coagi-las a números também. Veja Vamos fazer um mapa para exemplos.

    Se seus dados estiverem em um arquivo CSV ou TSV separado, use topojson -e (além de -p) para especificar um arquivo de propriedades externo que possa ser associado aos seus recursos geográficos. Fazendo o exemplo do wiki, se você tiver um arquivo 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

    Usando -e, você pode mapear isso para uma propriedade de saída numérica denominada “desemprego”:

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

    Um exemplo dessa abordagem é a população de Kentucky choropleth, bl.ocks.org/5144735 .

mbostock
fonte
2
E aqui estava eu ​​fazendo minhas perguntas difíceis de mapeamento D3 no stackoverflow em vez de gis.stackexchange, porque pensei que havia mais experiência lá - e então o próprio mestre responde à minha pergunta aqui. =) Bem, isso faz duas coisas que aprendi hoje. Obrigado!
Mittenchops
3

Boa pergunta. Um dos exemplos que você forneceu parece fazer o truque, embora seja difícil de seguir.

Você observará que o exemplo possui dois arquivos de dados externos, us.json e desemprego.tsv . Você pode pensar no desemprego.tsv como seu perigo.csv; us.json são os recursos geográficos aos quais você deseja associar parâmetros do danger.csv. Este último, unemployment.tsv, tem ide ratecampos onde a idé o mesmo que o idde us.json.

É no cliente com D3 que você deve mesclar seus dados e recursos , pelo menos neste exemplo. É no cliente que a taxa de desemprego, neste exemplo, é associada aos recursos do condado, usando a função d3.map () . É aqui que ele é inicializado:

var rateById = d3.map();

E é aqui que rateé mapeado para 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 admitir que não sei para que queue()serve, mas não é importante para esta discussão. O que é importante observar é que o idcampo em cada característica do condado é substituído pelo desemprego rate. o rateé agora acessível pelo identificador partilhada id( EDIT: Como @ blord-Castillo pontos para fora, isto é, na verdade, a geração de uma nova matriz associativa, ou hash da chave, onde o rateé mapeado para oid ). É aqui que rateé chamado para fins de simbologia (aqui, classes CSS predefinidas estão disponíveis para cada quantil):

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

Onde a quantize()função retorna o nome da classe CSS que deve ser usada para estilizar esse recurso (município) com base em sua taxa de desemprego, que agora é definida no idcampo do recurso .

Arthur
fonte
fila permite o carregamento paralelo assíncrono das fontes de dados em vez do carregamento serial.
Blord-castillo # 4/13
1
O que está acontecendo nesse exemplo é que rateById é um hash da chave. Nenhuma alteração é feita nos recursos do país e os dados us.json são intocados. Em vez disso, o desemprego.tsv é convertido em um hash de chave chamado 'rateById'. rateById.set () é repetido em desemprego.tsv para que uma chave seja inserida para cada ID em desemprego.tsv (não em us.json) e o valor dessa chave seja definido no campo de taxa para esse ID em desemprego.tsv . Mais tarde, rateById.get () é chamado para usar o hash para procurar a taxa de desemprego por id; esse valor é usado para definir o estilo nos recursos us.json e depois descartado.
Blord-castillo # /
Por que isso substitui / o ID pela taxa em vez de anexá-lo como um atributo em outro lugar? Isso parece dificultar a seleção mais tarde.
Mittenchops
1
Não substitui o ID pela taxa. Ele cria um hash de pesquisa do id para a taxa.
Blord-castillo # 4/13
2

Primeiro, a primeira linha do seu csv deve ser uma lista separada por vírgula de nomes de colunas para usar esse método. Se isso não for possível, adicione um comentário sobre isso e verei se consigo descobrir como usar em d3.csv.parseRowsvez de d3.csv.parse. d3.csv.parseé chamado pela função avaliador em .defer(function, url, assessor).

Vou assumir que seu arquivo agora está assim:

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

Com isso, você pode criar um hash de pesquisa do ISO3 para o nível de perigo.

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

Passo a passo do código

var dangerByISO3 = d3.map();

Primeiro, você cria um objeto d3.map () que funcionará como seu hash da chave e o armazena na variável dangerByISO3.

queue()

Use fila para carregamento paralelo.

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

Carregue seu topojson como o primeiro argumento a ser passado para a função de espera (após erro). Observe o estilo aqui em que esta função está encadeada queue(), mas listada em uma linha separada (não há ponto e vírgula final queue()).

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

Duas coisas estão acontecendo aqui. Primeiro, você está carregando danger.csv como seu segundo argumento a ser passado para a função de espera. Como você verá abaixo, esse argumento não é realmente usado. Em vez disso, é fornecido um argumento de avaliador para a função de carregamento, d3.csv. Esse avaliador processará cada linha do csv. Nesse caso, chamamos a função set em dangerByISO3 para que, para cada combinação de uma isochave, definamos levelcomo o valor correspondente a essa chave. A +d.levelnotação usa unário +para coagir o valor de d.level a um número.

.await(ready);

Depois que as duas fontes de dados são carregadas, elas são passadas como dois argumentos separados para a função ready(). O primeiro argumento para o retorno de chamada é sempre o primeiro erro que ocorreu. Se nenhum erro ocorreu, nulo será passado como o primeiro argumento. O segundo argumento é a primeira fonte de dados (resultado da primeira tarefa) e o terceiro argumento é a segunda fonte de dados (resultado da segunda tarefa).

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

Esta é a função de retorno de chamada ready(). Primeiro, tomamos o errorargumento que deve ser nulo se as duas tarefas de carregamento forem concluídas com êxito (você deve realmente adicionar linguagem para capturar e manipular erros). A seguir, tomamos os dados do topojson como objeto countries. Esses dados devem ser processados ​​no corpo da função com algo parecido .data(topojson.feature(world,world.objects.countries).features). Como ready()não adota um terceiro argumento, o resultado da segunda tarefa, nosso csv, é simplesmente descartado. Nós o usamos apenas para criar o hash da chave e não precisamos dele depois disso.

blord-castillo
fonte
Sim, você está certo, meu CSV realmente parece um CSV bem formado, em vez da demonstração descuidada que eu postei. =) Desculpe, eu vou atualizar isso.
Mittenchops