Here's your premise:
- 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.
- 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).