KoGor
KoGor

Reputation: 237

Map with d3.js and TopoJSON, Albers Siberia projection

I'm trying to make a Choropleth with d3.js but I got stucked just at the beginning. I found a Shapefile and generated GeoJSON and TopoJson files from it just like here. The map uses Albers-Siberia projection. What I found about this projection:

Projection: Albers Equal-Area Conic

PROJ.4: +proj=aea +lat_1=52 +lat_2=64 +lat_0=0 +lon_0=105 +x_0=18500000 +y_0=0 +ellps=krass +units=m +towgs84=28,-130,-95,0,0,0,0 +no_defs

MapInfo: "Albers-Siberia", 9, 1001, 7, 105, 0, 64, 52, 18500000, 0.

So I got this code finally and it make nothing (and even freez up), what's wrong?

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Choropleth</title>
    <script type="text/javascript" src="d3/d3.v3.js"></script>
    <script type="text/javascript" src="d3/queue.v1.min.js"></script>
    <script type="text/javascript" src="d3/topojson.v0.min.js"></script>
</head>
<body>
    <h1>My Choropleth</h1>
    <script type="text/javascript">

        var width = 960,
            height = 500;

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

        var pr = d3.geo.albers()
            .center([105,0])
            .parallels([52, 64])
            .scale(1000);


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

        d3.json("map_rus_topo.json", function(error, map) {
         svg.append("path")
          .datum(topojson.object(map, map.objects.map_rus))
          .attr("d", path);
        });

    </script>
</body>

You can find all JSON files here.
And one more question: How can I reference to value of region field in my TopoJson file.

Upvotes: 6

Views: 5456

Answers (2)

Sidders
Sidders

Reputation: 131

Adding an answer to this thanks to @mbostock's response. My d3.js map was not showing up or displaying correctly with any projection, then realised through this thread that it was because it didn't conform to the WGS-84 Geographic Coordinate System. According to Datawrapper, TopoJSON or GeoJSON needs to be in the WGS-84 coordinate system.

Thankfully, there is a much quicker and easier way to fix this in 2022. Just upload your geographic file(s) to Mapshaper, open the console, and type in proj wgs84 and press 'enter'. To verify that the projection has changed, type in info and press enter again. You should find the following line in the code that appears: Proj.4: +proj=longlat +datum=WGS8 4. This won't work in every case - it depends on the coordinate system your shapefiles originally used. But it's worth a try.

The preview of your map in Mapshaper may change after the conversion, but as long as you project the map correctly within the d3.js Javascript code, the end result will not be distorted. I had to refer back to where I downloaded my shapefiles to get the info needed for the d3 projection function.

I hope this helps others in the same boat.

Upvotes: 0

mbostock
mbostock

Reputation: 51819

The first problem is that your GeoJSON file isn’t in degrees [longitude°, latitude°], otherwise known as EPSG:4326 or WGS 84. To convert your GeoJSON file to WGS 84, you first need to create a projection file, say albers.prj so that you can tell OGR what the source projection is.

+proj=aea +lat_1=52 +lat_2=64 +lat_0=0 +lon_0=105 +x_0=18500000 +y_0=0 +ellps=krass +units=m +towgs84=28,-130,-95,0,0,0,0 +no_defs

Then, “unproject” the GeoJSON file by converting it to WGS 84:

ogr2ogr -f GeoJSON -s_srs albers.prj -t_srs EPSG:4326 map_rus_wgs84_geo.json map_rus_geo.json

Now you can convert to TopoJSON in WGS 84, rather than projected coordinates. I’ve also taken the liberty of doing some simplification:

topojson -o map_rus_wgs84_topo.json -s 1e-7 -- russia=map_rus_wgs84_geo.json

The second problem is that your projection definition in D3 is incorrect. The d3.geo.albers projection has a default rotate and center that’s designed for a U.S.-centered map, so in addition to defining the center you’ll also need to override the default rotation. In fact, the +lon_0 (central meridian) projection parameter maps to the projection’s rotation, not the projection’s center. Giving:

var projection = d3.geo.albers()
    .rotate([-105, 0])
    .center([-10, 65])
    .parallels([52, 64])
    .scale(700)
    .translate([width / 2, height / 2]);

(I fudged with the center parameter to put Russia at the center of the viewport. You can compute this automatically if you prefer.) You should now see something like this:

Albers Siberia

It’s also possible to work with projected (Cartesian) coordinates in TopoJSON, and then define a d3.geo.path with a null (identity) projection, but I’ll leave that for a separate question.

Upvotes: 17

Related Questions