Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
234 views
in Technique[技术] by (71.8m points)

javascript - Drawing circles via d3js and converting coordinates

I have following d3js code that draws country map based on geojson via d3.json and renderMap function. I want to add cities to the map that will be represented as a circles. Cities will be in the same svg as a country map. Cities are in ua_cities_admin.csv and rendered via renderCity function.

What I don't understand is how to place data array from renderCity in the svg component and how to map city coordinates to country map.

Any help with this will be appreciated.

<!DOCTYPE html>
<meta charset="utf-8">
<head>
  <title>geoPath measures</title>
</head>

<body>
<div align="center">
<svg id="my_dataviz"></svg>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.9.1/d3.min.js"></script>

<script>
  var height = window.innerHeight - 100;
  var width = window.innerWidth - 100;
  var svg = d3.select('#my_dataviz')
    .attr("width", width)
    .attr("height", height);
  
function renderMap(data) {
  var projection = d3.geoIdentity().reflectY(true).fitSize([width, height], data)
  var geoGenerator = d3.geoPath().projection(projection);
  svg.append("g")
    .selectAll("path")
    .data(data.features)
    .enter()
    .append('path')
    .attr('d', geoGenerator)
    .attr('fill', 'steelblue');
};

function renderCity(data) {
  var projection = d3.geoIdentity().reflectY(true).fitSize([width, height], data)
  console.log(data)
  svg.selectAll("circle")
    .data(data)
    .enter()
    .append("circle")
    .attr("cx", 50)
    .attr("cy", 50)
    .attr("r", 3);
};

d3.json('https://raw.githubusercontent.com/EugeneBorshch/ukraine_geojson/master/Ukraine.json', renderMap);
d3.csv('ua_cities_admin.csv', renderCity);
</script>
</body>
</html>

ua_cities_admin.csv file has following fields:

  • City
  • lat
  • lng
question from:https://stackoverflow.com/questions/65936103/drawing-circles-via-d3js-and-converting-coordinates

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Here's your premise:

  1. You have two input data sets consisting of geographic data, a geojson file and a csv file. Both use latitude longitude as their coordinate system.
  2. You want to project them, but you want them to share the same projected coordinate system, so that they align with one another.

However, if you use two different projections you won't get alignment: any common coordinate in the two input datasets will be projected differently by each different projection.

The simplest way to fix this is to reuse the same projection for both datasets, and to use a geographic projection, something like:

var projection = d3.geoMercator()
var geoGenerator = d3.geoPath().projection(projection);

function renderMap(data) {
  projection.fitSize([width, height], data)   
  svg.append("g")
    .selectAll("path")
    .data(data.features)
    .enter()
    .append('path')
    .attr('d', geoGenerator)
    .attr('fill', 'steelblue');
};

function renderCity(data) {
  svg.selectAll("circle")
    .data(data)
    .enter()
    .append("circle")
    .attr("cx", d=>projection([d.lon,d.lat])[0])
    .attr("cy", d=>projection([d.lon,d.lat])[1])
    .attr("r", 3);
};

 d3.json("file", function(geojson) {
   d3.csv("file", function(csv) {
       renderMap(geojson);
       renderCity(csv);
    })
 })

I nested your requests because otherwise, whichever file loads first will be drawn first. We also need the geojson to be loaded first to set the projection data that will be used for the csv's circles.


Additional Detail

Projection.fitSize()

For reference, projection.fitSize() requires a valid geojson object. The data generated by d3.csv is an array of objects. We need to convert this to geojson if we want fitSize to work. Geojson point features look like:

{
  "type": "Feature",
  "geometry": {
    "type": "Point",
    "coordinates": [longitude, latitude]
  },
  "properties": { /* ... */ }
}

So to create geojson we need to process your data a bit:

var features = data.map(function(d) {
    return {     
      "type": "Feature",
      "geometry": {
         "type": "Point",
         "coordinates": [d.lng, d.lat]
      },
      "properties": { "name":d.city }
    }
})

But we do need to pass an object, not an array, so we need to create a geojson feature collection:

var featureCollection = { type:"FeatureCollection", features:features }

Projection vs Identity

Unlike d3.geoIdentity, a d3.geoProjection can be passed a coordinate to project. d3.geoIdenity can work, but with some tweaking to the above code; however, it may result in an abnormal shape or unfamiliar representation of the area of interest as it basically implements a Plate Carree projection: lat/long treated as planar with appropriate scaling/translating to center and size the map appropriately. A d3.geoProjection could be suitable here, projections also allow for fitSize() to be used.

If you wish for the same shape as d3.geoIdentity, you can use d3.geoEquirectangular() in place of d3.geoMercator in the code above.

Manually Setting Projection Parameters

fitSize() sets a identity's/projection's scale and translate - that's it.

You could define the projection once, rather than waiting to redefine its parameters once the data is loaded. If your data is static, you could extract the scale and translate values used by fitSize() by logging projection.scale() and projection.translate() after you run fitSize(), and then use those values to set the projection scale and translate yourself. This could mean you wouldn't need to wait for the geojson to load before drawing any circles (provided you ensure that you are not drawing the geojson over top of the circles).


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...