D3 V4 Правильное размещение пузыря на карте США. - PullRequest
1 голос
/ 21 сентября 2019

Я создаю карту США, и у меня есть серия АКТУАЛЬНЫХ координат некоторых мест в США.Я хотел бы поставить точку или пузырь в нужном месте на карте.Как мне масштабировать / перевести это?

Вот что я получаю:

https://imgur.com/a/KsxiFPv

С тем, что я пробовал:

function USAPlot(divid, data) {

    var margin = { top: 20, right: 20, bottom: 30, left: 50 },
        width = 1040 - margin.left - margin.right,
        height = 700 - margin.top - margin.bottom;

    // formatting the data
    data.forEach(function (d) {
        d.loc = d.location;
        d.count = d.count;
        d.lat = d.latitude;
        d.lon = d.longitude;
    });

    var svg = d3.select(divid)
        .append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
        ;
    var path = d3.geoPath();
    var projection = d3.geoMercator()       
        .scale(200)  
        .translate([margin.left + width / 2, margin.top + height / 2])

    d3.json("https://d3js.org/us-10m.v1.json", function (error, us) {
        if (error) throw error;

        svg.append("g")
            .attr("class", "states")
            .attr("fill-opacity", 0.4)
            .selectAll("path")
            .data(topojson.feature(us, us.objects.states).features)
            .enter().append("path")
            .attr("d", path);

        svg.append("path")
            .attr("class", "state-borders")
            .attr("d", path(topojson.mesh(us, us.objects.states, function (a, b) { return a !== b; })));
    });


    svg.selectAll("myCircles")
        .data(data)
        .enter()
        .append("circle")
        .attr("cx", function (d) { return projection([d.lon, d.lat])[0]; })
        .attr("cy", function (d) { return projection([d.lon, d.lat])[1]; })
        .attr("r", 14)  //first testing with fixed radius and then will scale acccording to count
        .style("fill", "69b3a2")
        .attr("stroke", "#69b3a2")
        .attr("stroke-width", 3)
        .attr("fill-opacity", 1);

}

У меня естьПонятия не имею, падают ли эти пузыри на то место, которое я определенно ищу.

1 Ответ

0 голосов
/ 21 сентября 2019

Что касается метода тестирования для определения правильности расположения объектов, попробуйте легко определить ориентиры, я использую Сиэтл и Майами ниже - они находятся на противоположных сторонах области интереса, и это должно бытьЛегко сказать, находятся ли они не в том месте (в воде или на суше).

Я не уверен, где они должны упасть, поскольку у меня нет координат, но я могу сказать вам,они не там, где должны быть.

Причина, по которой я могу это знать, заключается в том, что вы используете две разные проекции для своих данных.

Проекция Меркатора

Вы определяете одну из проекций и используете ее для позиционирования точек:

var projection = d3.geoMercator()       
    .scale(200)  
    .translate([margin.left + width / 2, margin.top + height / 2])

Это проекция Меркатора с центром в [0 °, 0 °] (по умолчанию).Вот мир, спроектированный с этой проекцией (с полем и SVG такого же размера):

enter image description here

D3 GᴇᴏMᴇʀᴄᴀᴛᴏʀ Wɪᴛʜ Cᴇɴᴛᴇʀ [0,0] ᴀɴᴅ Sᴄᴀʟᴇ200

Вы проецируете координаты для кругов на основе этой проекции.

Для воспроизводимости, вот фрагмент - вы должны просмотреть в полноэкранном режиме:

  var margin = { top: 20, right: 20, bottom: 30, left: 50 },
  width = 1040 - margin.left - margin.right,
  height = 700 - margin.top - margin.bottom;
		
d3.json("https://cdn.jsdelivr.net/npm/world-atlas@2/land-50m.json").then(function(json) {
		
  var svg = d3.select("body")
    .append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)		
	
  var projection = d3.geoMercator()       
    .scale(200)  
    .translate([margin.left + width / 2, margin.top + height / 2])

  var path = d3.geoPath().projection(projection);
		
  svg.append("g")
     .attr("class", "states")
     .attr("fill-opacity", 0.4)
     .selectAll("path")
     .data(topojson.feature(json, json.objects.land).features)
     .enter().append("path")
     .attr("d", path);
	})
		
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/3.0.2/topojson.js"></script>

Таинственная проекция

Вторая проекция не очевидна.Если вы посмотрите на фрагмент, использованный для создания приведенного выше изображения, вы заметите, что он назначает проекцию пути:

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

Это так, что путь преобразует каждую географическую координату (сферическую широту / долготупару) к правильной координате на экране (декартово значение x, y пикселя): координата [-17 °, 85 °] будет преобразована во что-то вроде [100px, 50px].

В вашемвопрос, который вы просто используете:

var path = d3.geoPath();

Вы не назначаете проекцию пути - поэтому d3.geoPath () просто строит каждую вершину / точку в геойсоне / топойсоне так, как если бы координата содержала координаты пикселей:координаты [100px, 50px] в геойсоне / топойсоне нанесены на SVG при x = 100, y = 50.

Несмотря на то, что вы не используете проекцию, ваш график в штатах США соответствует ожидаемому.Почему?Потому что геойсон / топойсон был уже спроектирован.Поскольку он был предварительно спроектирован, нам не нужно использовать проекцию при построении графика с помощью D3.

Предварительно спроецированная геометрия может быть полезна, поскольку для ее рисования требуется меньше вычислений, что приводит к более высокой скорости рендеринга, но при этом требуется меньшая гибкость (см. здесь ).

Если мы наложим вашу предварительно спроецированную геометрию на геометрию, которую вы проецируете с помощью d3.geoProjection, мы получим:

enter image description here

Естественно, вы можете увидеть тамнет смысла, что то же самое между двумя.Следовательно, вы не проецируете точки, чтобы они правильно перекрывали предварительно спроецированные геометрии.

Cᴏᴍᴘᴀʀɪsᴏɴ ʙᴇᴛᴡᴇᴇɴ ᴛʜᴇ ᴛᴡᴏ ᴘʀᴏᴊᴇᴄᴛɪᴏɴs

Фрагмент для воспроизведения:

var margin = { top: 20, right: 20, bottom: 30, left: 50 },
        width = 1040 - margin.left - margin.right,
        height = 700 - margin.top - margin.bottom;
		
   var svg = d3.select("body")
		  .append("svg")
          .attr("width", width + margin.left + margin.right)
          .attr("height", height + margin.top + margin.bottom)	

	d3.json("https://cdn.jsdelivr.net/npm/world-atlas@2/land-50m.json").then(function(json) {
		
        var projection = d3.geoMercator()       
          .scale(200)  
          .translate([margin.left + width / 2, margin.top + height / 2])

		var path = d3.geoPath().projection(projection);
		
		svg.append("g")
            .attr("fill-opacity", 0.4)
            .selectAll("path")
            .data(topojson.feature(json, json.objects.land).features)
            .enter().append("path")
            .attr("d", path);
	
	})
	
	d3.json("https://d3js.org/us-10m.v1.json").then(function(us) {

		var path = d3.geoPath();

        svg.append("g")
            .attr("fill-opacity", 0.4)
            .selectAll("path")
            .data(topojson.feature(us, us.objects.states).features)
            .enter().append("path")
            .attr("d", path);
		
	})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/3.0.2/topojson.js"></script>

Неудовлетворительное решение

Без метаданных, объясняющих, какую проекцию и систему координат использует геойсон / топойсон, мы обычно не можем дублировать эту проекцию для наложения других функций.

В этом случае, однако, если мы внимательно посмотрим на нанесенные на карту штаты США, то увидим, что проекция Альберса использовалась для предварительного проектирования контуров штатов.

Иногда мы можем угадать параметры проекции.Поскольку я довольно знаком с этим файлом (), я могу сказать, что он использует следующие параметры:

d3.geoAlbersUsa()
  .scale(d3.geoAlbersUsa().scale()*6/5)
  .translate([480,300]);

Вот пример, показывающий перекрытие Майами и Сиэтла:

  var width = 960,height = 600;
		
var svg = d3.select("body")
	.append("svg")
  .attr("width",width)
  .attr("height",height);

d3.json("https://d3js.org/us-10m.v1.json").then(function(us) {

  var path = d3.geoPath();
  var projection = d3.geoAlbersUsa()
    .scale(d3.geoAlbersUsa().scale()*6/5)
    .translate([width/2,height/2]);
    
  svg.append("g")
    .attr("fill-opacity", 0.4)
    .selectAll("path")
    .data(topojson.feature(us, us.objects.states).features)
    .enter().append("path")
    .attr("d", path);
    
    var places = [
      [-122.3367534,47.5996582],
      [-80.1942949,25.7645783]
    ]
    
    svg.selectAll(null)
      .data(places)
      .enter()
      .append("circle")
      .attr("r", 3)
      .attr("transform", function(d) {
        return "translate("+projection(d)+")";
      })
		
	})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/3.0.2/topojson.js"></script>

Но у этого есть обратная сторона: он очень тупой в усыновлении.Например, для экранов других размеров, трансляций, центров, масштабов и т. д. Предварительно спроектированная геометрия также создает много путаницы в сочетании с непроектированной геометрией.Например, этот вопрос демонстрирует общее разочарование по поводу правильного определения размера и центрирования предварительно спроецированной геометрии.

Лучшее решение

Лучшее решение - использовать один прогноз для всего.Либо сначала спроектируйте все сначала (что немного сложнее), либо спроецируйте все на лету (для браузера это действительно не займет много времени).Это становится понятнее и проще при изменении визуализации или географических данных.

Чтобы проецировать все одинаково, вам необходимо убедиться, что все ваши данные не спроецированы, то есть используются широта / долготапары для его координат / координатного пространства.Поскольку ваш американский JSON уже спроектирован, нам нужно найти другое, возможно:

И мы просто пропустим все через проекцию:

Фрагмент не будет загружать ресурс, но здесь a bl.ock с кодом, показанным ниже:

var width =960,height = 600;

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

d3.json("us.json").then(function(us) {

  var projection = d3.geoAlbersUsa()
    .scale(150)
    .translate([width/2,height/2]);
   var path = d3.geoPath().projection(projection);

  svg.append("g")
    .attr("fill-opacity", 0.4)
    .selectAll("path")
    .data(topojson.feature(us, us.objects.states).features)
    .enter().append("path")
    .attr("d", path);

    var places = [
      [-122.3367534,47.5996582],
      [-80.1942949,25.7645783]
    ]

    svg.selectAll(null)
      .data(places)
      .enter()
      .append("circle")
      .attr("r", 3)
      .attr("transform", function(d) {
        return "translate("+projection(d)+")";
      })

})
...