Circles in Map Displayed Incorrect Location in D3 V4

I am using a tutorial to learn how to generate maps in D3.v3, but I am using D3.v4. I am just trying to get some circles to appear on the map (see below). The code works except that the circles are over Nevada and should be in the Bay Area. I imagine this is a mismatch between projections of the map and the projected coordinates. I am not sure what projection the map is in, but I have tried to force it to be albersUsa (see commented out commands where I generate path) but this causes the entire map to disappear. Any help would be appreciated!

<!DOCTYPE html>

<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/topojson.v2.min.js"></script>
<script>

var w = 960,
    h = 600;
var projection = d3.geoAlbersUsa();
var path = d3.geoPath()
             //.projection(projection)

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

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

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

   svg.append("path")
      .attr("class", "state-borders")
      .attr("d", path(topojson.mesh(us, us.objects.states)))

   svg.append("path")
      .attr("class", "county-borders")
      .attr("d", path(topojson.mesh(us, us.objects.counties)));

      aa = [-122.490402, 37.786453];
  	  bb = [-122.389809, 37.72728];

   svg.selectAll("circle")
      .data([aa,bb]).enter()
      .append("circle")
      .attr("cx", function (d) { return projection(d)[0]; })
      .attr("cy", function (d) { return projection(d)[1]; })
      .attr("r", "8px")
      .attr("fill", "red")
    });

</script>

Answers:

Answer

Your US json is already projected, and to show it you use a null projection:

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

Without defining a projection, your topojson/geojson coordinates will be translated to straight pixel coordinates. It just so happens that this particular topojson file has pixel coordinates that are within [0,0] and [960,600], almost the same size as a default bl.ock view. Without knowing the projection used too create that file you cannot replicated that projection to align geographic features to your data. Unless you place your features with pixel values directly and skip the projection altogether (not useful for points not near identifiable landmarks or where precision matters).

Your US topojson features disappear when projecting with a geoUsaAlbers() because you are taking pixel coordinates on a plane and transforming them to svg coordinates as though they were points on a three dimensional globe (d3 projections expect latitude longitude pairs).

Instead, use a topojson or geojson that is unprojected. That is to say it contains latitude/longitude pairs and project that data along with your points. See this bl.ock for an example with unprojected (lat/long pairs) json for the US using your code (but assigning a projection to path).

To check if you have latitude/longitude pairs you can view the geometry of these features in a geojson file easily and see if the values are valid long, lat points. For topojson, the topojson library converts features to geojson, so you can view the geometries after this conversion.

Here's an unprojected topojson of the US: https://bl.ocks.org/mbostock/raw/4090846/us.json


Let's say you really wanted to use the same topojson file though, well we can probably deduce the projection it uses. First, I'll show the difference between your projected points (by using an unprojected outline of the US) and the already projected topojson (the unprojected topojson is projected with d3.geoAlbersUsa() and the projected with a null projection):

enter image description here

Chances are the projection d3.geoAlbersUsa is optimized for a bl.ocks.org default viewport, 960x500. The unprojected dataset has a bounding box of roughly 960x600, so perhaps if we increase the scale by a factor of 600/500 and adjust the translate we can align our features in an svg that is 960x600:

var projection = d3.geoAlbersUsa();
var scale = projection.scale() * 600 / 500;
projection.scale(scale).translate([960/2,600/2])
var projectedPath = d3.geoPath().projection(projection);

And, this appears to align fairly well, I can't see the difference between the two:

enter image description here

Here's a block showing the aligned features.

But as I mention in the comments, even if you can align the features: any zoom or centering would be problematic as you need to use a geoTransform on already projected data but a geoProjection on the raw geographic data. Using all (uniformly) projected data or all unprojected data makes life simpler.

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.