Подтвердить что ты не робот

Как нарисовать линию/ссылку между двумя точками на карте D3 на основе широты/долготы?

Я пытаюсь создать карту 10 основных объектов НАСА в D3. Я успешно создал базовую карту Соединенных Штатов и добавленные логотипы NASA в каждом из центральных мест на основе CSV с широтой и долготой. Тем не менее, я не могу найти элегантный способ рисовать линии/ссылки/дуги/соединения между точками на карте.

В приведенном ниже коде я нарисовал линию между GSFC и KSC (используя "var = places", "var = route" и "svg.append(" путь ")), но он находится на SVG слой, поэтому он находится поверх логотипов (что выглядит ужасно) и не масштабируется (или уйти тоже будет хорошо) при нажатии для увеличения масштаба. Я хотел бы иметь возможность рисовать связи между центрами на основе данных широты и долготы из .csv.

<!DOCTYPE html>
<meta charset="utf-8">
<style>

.background {
  fill: none;
  pointer-events: all;
}

#states {
  fill: #aaaaaa;
}

#states .active {
  fill: #ff0000;
  fill-opacity: .5;
}

#state-borders {
  fill: none;
  stroke: #ffffff;
  stroke-width: 1.5px;
  stroke-linejoin: round;
  stroke-linecap: round;
  pointer-events: none;
}

path.link {
  fill: none;
  stroke: #666666;
  stroke-width: 1.5px;
}

.stroke {
  fill: none;
  stroke: #000;
  stroke-width: 3px;
}

.fill {
  fill: #fff;
}

.graticule {
  fill: none;
  stroke: #777;
  stroke-width: .5px;
  stroke-opacity: .5;
}

.route {
  fill: none;
  stroke: blue;
  stroke-width: 3px;
}

</style>
<body>
    <h2>
      <span>NASA Centers</span>
    </h2>

<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/d3.geo.projection.v0.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script>

var width = 1000,
    height = 600,
    centered;

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

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

var graticule = d3.geo.graticule();

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

var g = svg.append("g");

var places = {
    GSFC: [-76.852587, 38.991621],
    KSC: [-80.650813, 28.524963]
    };

var route = {
  type: "LineString",
  coordinates: [
    places.GSFC,
    places.KSC
  ]
};

var point = svg.append("g")
    .attr("class", "points")
  .selectAll("g")
    .data(d3.entries(places))
  .enter().append("g")
    .attr("transform", function(d) { return "translate(" + projection(d.value) + ")"; });

point.append("text")
    .attr("y", 5)
    .attr("dx", "1em")
    .text(function(d) { return d.key; });

d3.json("us.json", function(error, us) {
    g.append("g")
      .attr("id", "states")
    .selectAll("path")
      .data(topojson.feature(us, us.objects.states).features)
    .enter().append("path")
      .attr("d", path)
      .on("click", clicked);

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

    d3.csv("nasacenters.csv", function(error, data) {
        g.selectAll("image").data([0])
           .data(data)
           .enter()
           .append("image")
            .attr("xlink:href", "nasalogo.png")
            .attr("width", "30")
            .attr("height", "30")
            .attr("x", function(d) {
                   return projection([d.lon, d.lat])[0]-15;
            })
            .attr("y", function(d) {
                   return projection([d.lon, d.lat])[1]-15;
            })

        svg.append("path")
          .datum(route)
          .attr("class", "route")
          .attr("d", path)
          .style("opacity", 0.5);

    });

});

function clicked(d) {
  var x, y, k;

  if (d && centered !== d) {
    var centroid = path.centroid(d);
    x = centroid[0];
    y = centroid[1];
    k = 4;
    centered = d;
  } else {
    x = width / 2;
    y = height / 2;
    k = 1;
    centered = null;
  }

  g.selectAll("path")
      .classed("active", centered && function(d) { return d === centered; });

  g.transition()
      .duration(750)
      .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")")
      .style("stroke-width", 1.5 / k + "px");
}

    </script>
  </body>
</html>

Файл .csv находится в следующем формате:

code,center,lat,lon
GSFC,Goddard Space Flight Center,38.991621,-76.852587
KSC,Kennedy Space Center,28.524963,-80.650813
JPL,Jet Propulsion Laboratory,34.200463,-118.176008
DFRC,Dryden Flight Research Center,34.613714,-118.076790
GRC,Glenn Research Center,41.415891,-81.861774
MSFC,Marshall Space Flight Center,34.646554,-86.674368
ARC,Ames Research Center,37.409574,-122.064292
LaRC,Langley Research Center,37.092123,-76.376230
JSC,Johnson Space Center,29.551508,-95.092256
SSC,Stennis Space Center,30.363692,-89.600036
4b9b3361

Ответ 1

Я немного изменил ваш пример в зависимости от описанных проблем: http://bl.ocks.org/erikhazzard/6201948

Похоже, есть три проблемы:

  • Контуры рисуются поверх значка. Чтобы исправить это, вы можете изменить порядок добавления элементов в группу или добавить подгруппы в свою основную группу g, гарантируя, что порядок добавления групп будет соответствовать порядку, в котором вы хотите, чтобы все отображалось.

  • Пути между точками не масштабируются при масштабировании карты. Чтобы исправить это, обязательно добавьте все в группу, в которую вы изменяете функцию clicked(). В этом случае ваша группа g будет увеличена, поэтому, если вы добавите пути к группе g, а не к svg, то также будут увеличиваться и пути. В приведенном примере текст также не увеличивается - это потому, что он добавляется непосредственно в SVG, а не в группу g, которая преобразуется.

  • Контуры не создаются автоматически из данных. Чтобы исправить это, вы можете сгенерировать массив, содержащий объекты LineString, из данных. Например,

        for(var i=0, len=data.length-1; i<len; i++){
        // (note: loop until length - 1 since we're getting the next
        //  item with i+1)
            links.push({
                type: "LineString",
                coordinates: [
                    [ data[i].lon, data[i].lat ],
                    [ data[i+1].lon, data[i+1].lat ]
                ]
            });
        }
    

    Затем выполните стандартный шаблон соединения данных и перейдите в список links к данным. Когда вы передаете path в качестве атрибута d, он будет генерировать большую дугу, основанную на координатах для каждого элемента:

    // Standard enter / update 
    var pathArcs = arcGroup.selectAll(".arc")
        .data(links);
    
    //enter
    pathArcs.enter()
        .append("path").attr({
            'class': 'arc'
        }).style({ 
            fill: 'none',
        });
    
    //update
    pathArcs.attr({
            //d is the points attribute for this path, we'll draw
            //  an arc between the points using the arc function
            d: path
        })
        .style({
            stroke: '#0000ff',
            'stroke-width': '2px'
        })
    

В моем примере (http://bl.ocks.org/enoex/6201948) Я добавил переход на большие дуговые пути, чтобы проиллюстрировать, как путь рисуется на основе порядка пар координат, переданных в объект связей.

Надеюсь, что это поможет!