Bir geoJSON nesnesi verilen bir haritayı d3'te ortalayın


140

Şu anda d3'te çizeceğiniz bir geoJSON nesneniz varsa, onu ölçeklemek ve ortalamak için onu istediğiniz boyuta getirmek ve çevirmek için çevirmek zorundasınız. Bu çok sıkıcı bir deneme yanılma görevidir ve bu değerleri elde etmenin daha iyi bir yolunu bilip bilmediğini merak ediyordum.

Yani, örneğin, bu kod varsa

var path, vis, xy;
xy = d3.geo.mercator().scale(8500).translate([0, -1200]);

path = d3.geo.path().projection(xy);

vis = d3.select("#vis").append("svg:svg").attr("width", 960).attr("height", 600);

d3.json("../../data/ireland2.geojson", function(json) {
  return vis.append("svg:g")
    .attr("class", "tracts")
    .selectAll("path")
    .data(json.features).enter()
    .append("svg:path")
    .attr("d", path)
    .attr("fill", "#85C3C0")
    .attr("stroke", "#222");
});

Azar azar nasıl .scale (8500) ve .translate ([0, -1200]) elde edebilirim?


Yanıtlar:


134

Aşağıdakiler yaklaşık olarak istediğinizi yapıyor gibi görünüyor. Ölçekleme iyi görünüyor. Haritama uygularken küçük bir ofset var. Bu küçük ofset muhtemelen haritayı ortalamak için translate komutunu kullanırken, büyük olasılıkla center komutunu kullanmam gerektiğinden kaynaklanır.

  1. Bir projeksiyon oluşturun ve d3.geo.path
  2. Mevcut projeksiyonun sınırlarını hesapla
  3. Ölçeği ve çeviriyi hesaplamak için bu sınırları kullanın
  4. Projeksiyonu yeniden oluşturun

Kodda:

  var width  = 300;
  var height = 400;

  var vis = d3.select("#vis").append("svg")
      .attr("width", width).attr("height", height)

  d3.json("nld.json", function(json) {
      // create a first guess for the projection
      var center = d3.geo.centroid(json)
      var scale  = 150;
      var offset = [width/2, height/2];
      var projection = d3.geo.mercator().scale(scale).center(center)
          .translate(offset);

      // create the path
      var path = d3.geo.path().projection(projection);

      // using the path determine the bounds of the current map and use 
      // these to determine better values for the scale and translation
      var bounds  = path.bounds(json);
      var hscale  = scale*width  / (bounds[1][0] - bounds[0][0]);
      var vscale  = scale*height / (bounds[1][1] - bounds[0][1]);
      var scale   = (hscale < vscale) ? hscale : vscale;
      var offset  = [width - (bounds[0][0] + bounds[1][0])/2,
                        height - (bounds[0][1] + bounds[1][1])/2];

      // new projection
      projection = d3.geo.mercator().center(center)
        .scale(scale).translate(offset);
      path = path.projection(projection);

      // add a rectangle to see the bound of the svg
      vis.append("rect").attr('width', width).attr('height', height)
        .style('stroke', 'black').style('fill', 'none');

      vis.selectAll("path").data(json.features).enter().append("path")
        .attr("d", path)
        .style("fill", "red")
        .style("stroke-width", "1")
        .style("stroke", "black")
    });

9
Hey Jan van der Laan Bu yanıt için sana hiç teşekkür etmedim. Bu arada cömertlik ayırabilseydim gerçekten iyi bir yanıt. Bunun için teşekkürler!
climboid

Bunu uygularsam sınır = sonsuzluk elde ederim. Bunun nasıl çözülebileceğine dair bir fikrin var mı?
Simke Nys

@SimkeNys Bu, burada belirtilenle aynı sorun olabilir stackoverflow.com/questions/23953366/… Burada belirtilen çözümü deneyin.
Jan van der Laan

1
Merhaba Jan, kodun için teşekkürler. Bazı GeoJson verileri ile örnek denedim ama işe yaramadı. Bana neyi yanlış yaptığımı söyleyebilir misin? :) GeoJson verilerini yükledim: onedrive.live.com/…
user2644964

1
D3 v4'te projeksiyon uydurma yerleşik bir yöntemdir: projection.fitSize([width, height], geojson)( API belgeleri ) - aşağıdaki @dnltsk'ın cevabına bakın.
Florian Ledermann

173

Cevabım Jan van der Laan'a yakın, ancak coğrafi centroid'i hesaplamanıza gerek olmadığı için işleri biraz basitleştirebilirsiniz; sadece sınırlayıcı kutuya ihtiyacınız var. Ve ölçeklenmemiş, çevrilmemiş bir birim projeksiyon kullanarak, matematiği basitleştirebilirsiniz.

Kodun önemli kısmı şudur:

// Create a unit projection.
var projection = d3.geo.albers()
    .scale(1)
    .translate([0, 0]);

// Create a path generator.
var path = d3.geo.path()
    .projection(projection);

// Compute the bounds of a feature of interest, then derive scale & translate.
var b = path.bounds(state),
    s = .95 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height),
    t = [(width - s * (b[1][0] + b[0][0])) / 2, (height - s * (b[1][1] + b[0][1])) / 2];

// Update the projection to use computed scale & translate.
projection
    .scale(s)
    .translate(t);

Özelliğin sınırlama kutusunu birim projeksiyonunda derledikten sonra , sınırlama kutusunun ( ve ) en boy oranını kanvasın ( ve ) en boy oranıyla karşılaştırarak uygun ölçeği hesaplayabilirsiniz . Bu durumda, sınırlayıcı kutuyu da% 100 yerine tuvalin% 95'ine ölçeklendirdim, bu nedenle kenarlarda konturlar ve çevresindeki özellikler veya dolgu için biraz daha fazla yer var.b[1][0] - b[0][0]b[1][1] - b[0][1]widthheight

Ardından , sınırlayıcı kutunun ( ve ) merkezini ve tuvalin ( ve ) merkezini kullanarak çeviriyi hesaplayabilirsiniz . Sınırlayıcı kutu ünite projeksiyon koordinatlarında olduğundan, ölçek ( ) ile çarpılması gerektiğini unutmayın .(b[1][0] + b[0][0]) / 2(b[1][1] + b[0][1]) / 2width / 2height / 2s

Örneğin, bl.ocks.org/4707858 :

sınırlayıcı kutuya proje

Bir koleksiyondaki projeksiyonu ayarlamadan belirli bir özelliğe nasıl yakınlaştırılacağı, yani yakınlaştırmak ve uzaklaştırmak için projeksiyonu geometrik bir dönüşümle birleştirmekle ilgili bir soru var . Bu, yukarıdakiyle aynı prensipleri kullanır, ancak matematik biraz farklıdır, çünkü geometrik dönüşüm (SVG "dönüşüm" özelliği) coğrafi projeksiyon ile birleştirilmiştir.

Örneğin, bl.ocks.org/4699541 :

sınırlayıcı kutuyu yakınlaştır


2
Yukarıdaki kodda, özellikle sınırların endekslerinde birkaç hata olduğunu belirtmek istiyorum. Şöyle görünmelidir: s = (0.95 / Math.max ((b [1] [0] - b [0] [0]) / genişlik, (b [1] [1] - b [0] [0] ) / height)) * 500, t = [(genişlik - s * (b [1] [0] + b [0] [0]))) / 2, (yükseklik - s * (b [1] [1] + b [0] [1])) / 2];
iros

2
@iros - * 500Burada yabancı gibi görünüyor ... Ayrıca, ölçek hesaplamada b[1][1] - b[0][0]olması gerekir b[1][1] - b[0][1].
nrabinowitz


5
Yani:b.s = b[0][1]; b.n = b[1][1]; b.w = b[0][0]; b.e = b[1][0]; b.height = Math.abs(b.n - b.s); b.width = Math.abs(b.e - b.w); s = .9 / Math.max(b.width / width, (b.height / height));
Herb Caudill

3
Bunun gibi bir topluluk yüzünden D3, çalışmak için çok büyük bir sevinç. Müthiş!
arunkjn

55

D3 v4 veya v5 ile çok daha kolay!

var projection = d3.geoMercator().fitSize([width, height], geojson);
var path = d3.geoPath().projection(projection);

ve sonunda

g.selectAll('path')
  .data(geojson.features)
  .enter()
  .append('path')
  .attr('d', path)
  .style("fill", "red")
  .style("stroke-width", "1")
  .style("stroke", "black");

Keyfini çıkarın, Şerefe


2
Umarım bu cevap daha çok oylanır. d3v4Bir süredir çalışıyor ve bu yöntemi keşfettim.
Mark

2
Nereden ggeliyor? Bu svg konteyneri mi?
Tschallacka

1
Tschallacka tag golmalı<g></g>
giankotarola

1
Utanç bu kadar aşağı ve 2 kalite cevaplar sonra. Bunu kaçırmak kolaydır ve diğer cevaplardan çok daha basittir.
Kurt

1
Teşekkür ederim. V5 de çalışıyor!
Chaitanya Bangera

53

D3'te yeniyim - nasıl anladığımı açıklamaya çalışacağım ama her şeyi doğru yaptığımdan emin değilim.

İşin sırrı, bazı yöntemlerin kartografik uzayda (enlem, boylam) ve diğerlerinin kartezyen uzayda (ekranda x, y) çalışacağını bilmektir. Kartografik uzay (gezegenimiz) (neredeyse) küreseldir, kartezyen uzay (ekran) düzdür - birbirini eşlemek için projeksiyon adı verilen bir algoritmaya ihtiyacınız vardır . Bu alan, büyüleyici projeksiyonlar konusuna ve küresel düzlemi düzleme dönüştürmek için coğrafi özellikleri nasıl deforme ettiklerine çok kısa; bazıları açıları korumak için tasarlanmış, diğerleri mesafeleri korumak ve benzeri - her zaman bir uzlaşma var (Mike Bostock'un çok çeşitli örnekleri var ).

resim açıklamasını buraya girin

D3'te, projeksiyon nesnesinin harita birimlerinde verilen bir center özelliği / ayarlayıcısı vardır:

projection.center ([konumu])

Merkez belirtilirse, projeksiyonun merkezini belirtilen konuma, iki öğeli bir boylam ve enlem dizisi olarak ayarlar ve projeksiyonu döndürür. Merkez belirtilmezse, varsayılan olarak ⟨0 °, 0 ° to olan geçerli merkezi döndürür.

Ayrıca, projeksiyon merkezinin tuvale göre durduğu piksel olarak verilen çeviri de vardır:

projection.translate ([noktası])

Nokta belirtilirse, projeksiyonun çeviri farkını belirtilen iki elemanlı diziye [x, y] ayarlar ve projeksiyonu döndürür. Nokta belirtilmezse, varsayılan olarak [480, 250] olan geçerli çeviri dengesini döndürür. Çeviri dengesi, projeksiyon merkezinin piksel koordinatlarını belirler. Varsayılan çeviri ofseti, 960 × 500 alanın merkezine ⟨0 °, 0 °⟩ yerleştirir.

Bir özelliği tuvalde ortalamak istediğimde, projeksiyon merkezini özellik sınırlama kutusunun ortasına ayarlamak istiyorum - bu ülkem için (Brezilya) mercator (WGS 84, google haritalarında kullanılan) kullanırken, asla diğer projeksiyonlar ve yarım küreler kullanılarak test edilmemiştir. Diğer durumlar için ayarlamalar yapmanız gerekebilir, ancak bu temel ilkeleri çivilerseniz, iyi olacaksınız.

Örneğin, bir projeksiyon ve yol verildiğinde:

var projection = d3.geo.mercator()
    .scale(1);

var path = d3.geo.path()
    .projection(projection);

bounds, Yöntem pathgetiri sınırlama kutusu piksel . Piksel cinsinden boyutu harita birimlerindeki boyutla karşılaştırarak doğru ölçeği bulmak için kullanın (0,95 size genişlik veya yükseklik için en iyi uyuma göre% 5'lik bir kenar boşluğu verir). Buradaki temel geometri, çapraz olarak zıt köşeler verilen dikdörtgen genişliğini / yüksekliğini hesaplar:

var b = path.bounds(feature),
    s = 0.9 / Math.max(
                   (b[1][0] - b[0][0]) / width, 
                   (b[1][1] - b[0][1]) / height
               );
projection.scale(s); 

resim açıklamasını buraya girin

d3.geo.boundsHarita birimlerinde sınırlayıcı kutuyu bulmak için yöntemi kullanın :

b = d3.geo.bounds(feature);

Yansıtmanın merkezini sınırlayıcı kutunun ortasına ayarlayın:

projection.center([(b[1][0]+b[0][0])/2, (b[1][1]+b[0][1])/2]);

translateHaritanın merkezini kanvasın ortasına taşımak için yöntemi kullanın :

projection.translate([width/2, height/2]);

Şimdiye kadar, haritanın ortasındaki özelliğin% 5'lik bir kenar boşluğu ile yakınlaştırılmış olması gerekir.


Bir yerde bl.ocks var mı?
Hugolpz

Üzgünüm, bl.ocks veya gist yok, ne yapmaya çalışıyorsun? Tıkla ve yakınlaştır gibi bir şey mi? Yayınlayın ve kodunuza bir göz atabilirim.
Paulo Scardine

Bostock'un yanıtı ve resimleri , mühendise tüm kodu kopyalamama izin veren bl.ocks.org örneklerine bağlantılar sağlar. İş bitmiş. +1 ve harika resimleriniz için teşekkürler!
Hugolpz

4

Bir lat / lon çiftini kabul edebileceğiniz bir center () yöntemi vardır.

Anladığım kadarıyla, translate () yalnızca haritanın piksellerini tam anlamıyla hareket ettirmek için kullanılır. Ölçeğin ne olduğunu nasıl belirleyeceğimi bilmiyorum.


8
TopoJSON kullanıyorsanız ve tüm haritayı ortalamak istiyorsanız, JSON nesnesine bir bbox niteliği eklemek için topojson'u --bbox ile çalıştırabilirsiniz. Merkez için enlem / boylam koordinatları [(b [0] + b [2]) / 2, (b [1] + b [3]) / 2] olmalıdır (burada b, bbox değeridir).
Paulo Scardine


2

Haritamı ortalamak için internette sorunsuz bir şekilde bakıyordum ve Jan van der Laan ve mbostock'un cevabından ilham aldım. Svg için bir kap kullanıyorsanız jQuery kullanmanın daha kolay bir yolu. Dolgu / kenarlık vb. İçin% 95 kenarlık oluşturdum.

var width = $("#container").width() * 0.95,
    height = $("#container").width() * 0.95 / 1.9 //using height() doesn't work since there's nothing inside

var projection = d3.geo.mercator().translate([width / 2, height / 2]).scale(width);
var path = d3.geo.path().projection(projection);

var svg = d3.select("#container").append("svg").attr("width", width).attr("height", height);

Tam ölçeklendirme arıyorsanız, bu cevap sizin için çalışmaz. Ama benim gibi, bir kapta merkezileşen bir harita görüntülemek istiyorsanız, bu yeterli olmalı. Mercator haritasını görüntülemeye çalışıyordum ve bu yöntemin haritamı merkezileştirmede yararlı olduğunu buldum ve ihtiyacım olmadığından Antarktika bölümünü kolayca kesebilirim.


1

Haritayı kaydırmak / yakınlaştırmak için SVG'yi Leaflet üzerine yerleştirmeye bakmalısınız. SVG'yi dönüştürmekten çok daha kolay olacak. Bu örneğe bakın http://bost.ocks.org/mike/leaflet/ ve sonra broşürdeki harita merkezinin değiştirilmesi


Başka bir bağımlılık eklemek endişe ediyorsa, PAN ve ZOOM saf
d3'te

Bu cevap d3 ile gerçekten ilgilenmiyor. Haritayı d3'te de kaydırabilir / yakınlaştırabilirsiniz, broşür gerekli değildir. (Bunun eski bir gönderi olduğunu fark ettim, sadece cevaplara göz
atıyordu

0

Mbostocks'un cevabı ve Herb Caudill'in yorumu ile, bir mercator projeksiyonu kullandığımdan beri Alaska ile sorun yaşamaya başladım. Kendi amaçlarım için ABD Devletlerini projelendirmeye ve merkezlemeye çalıştığımı not etmeliyim. İki cevabı Jan van der Laan cevabı ile, yarım kürelerle örtüşen çokgenler (Doğu - Batı için 1'den büyük olan mutlak bir değerle sonuçlanan çokgenler) için aşağıdaki istisna dışında evlenmek zorunda olduğumu buldum:

  1. mercator'da basit bir projeksiyon oluşturun:

    projeksiyon = d3.geo.mercator (). ölçek (1). tercüme ([0,0]);

  2. yolu oluştur:

    yol = d3.geo.path (). projeksiyon (projeksiyon);

3. sınırlarımı oluştur:

var bounds = path.bounds(topoJson),
  dx = Math.abs(bounds[1][0] - bounds[0][0]),
  dy = Math.abs(bounds[1][1] - bounds[0][1]),
  x = (bounds[1][0] + bounds[0][0]),
  y = (bounds[1][1] + bounds[0][1]);

4. Alaska ve hemisferlerin üst üste geldiği durumlar için istisna ekleyin:

if(dx > 1){
var center = d3.geo.centroid(topojson.feature(json, json.objects[topoObj]));
scale = height / dy * 0.85;
console.log(scale);
projection = projection
    .scale(scale)
    .center(center)
    .translate([ width/2, height/2]);
}else{
scale = 0.85 / Math.max( dx / width, dy / height );
offset = [ (width - scale * x)/2 , (height - scale * y)/2];

// new projection
projection = projection                     
    .scale(scale)
    .translate(offset);
}

Umarım bu yardımcı olur.


0

Dikey ve yatay olarak ayarlamak isteyen insanlar için çözüm şudur:

  var width  = 300;
  var height = 400;

  var vis = d3.select("#vis").append("svg")
      .attr("width", width).attr("height", height)

  d3.json("nld.json", function(json) {
      // create a first guess for the projection
      var center = d3.geo.centroid(json)
      var scale  = 150;
      var offset = [width/2, height/2];
      var projection = d3.geo.mercator().scale(scale).center(center)
          .translate(offset);

      // create the path
      var path = d3.geo.path().projection(projection);

      // using the path determine the bounds of the current map and use 
      // these to determine better values for the scale and translation
      var bounds  = path.bounds(json);
      var hscale  = scale*width  / (bounds[1][0] - bounds[0][0]);
      var vscale  = scale*height / (bounds[1][1] - bounds[0][1]);
      var scale   = (hscale < vscale) ? hscale : vscale;
      var offset  = [width - (bounds[0][0] + bounds[1][0])/2,
                        height - (bounds[0][1] + bounds[1][1])/2];

      // new projection
      projection = d3.geo.mercator().center(center)
        .scale(scale).translate(offset);
      path = path.projection(projection);

      // adjust projection
      var bounds  = path.bounds(json);
      offset[0] = offset[0] + (width - bounds[1][0] - bounds[0][0]) / 2;
      offset[1] = offset[1] + (height - bounds[1][1] - bounds[0][1]) / 2;

      projection = d3.geo.mercator().center(center)
        .scale(scale).translate(offset);
      path = path.projection(projection);

      // add a rectangle to see the bound of the svg
      vis.append("rect").attr('width', width).attr('height', height)
        .style('stroke', 'black').style('fill', 'none');

      vis.selectAll("path").data(json.features).enter().append("path")
        .attr("d", path)
        .style("fill", "red")
        .style("stroke-width", "1")
        .style("stroke", "black")
    });

0

Bir Topojson'u nasıl merkezledim, burada özelliği çıkarmam gerekiyordu:

      var projection = d3.geo.albersUsa();

      var path = d3.geo.path()
        .projection(projection);


      var tracts = topojson.feature(mapdata, mapdata.objects.tx_counties);

      projection
          .scale(1)
          .translate([0, 0]);

      var b = path.bounds(tracts),
          s = .95 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height),
          t = [(width - s * (b[1][0] + b[0][0])) / 2, (height - s * (b[1][1] + b[0][1])) / 2];

      projection
          .scale(s)
          .translate(t);

        svg.append("path")
            .datum(topojson.feature(mapdata, mapdata.objects.tx_counties))
            .attr("d", path)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.