3
votes

I'm attempting to follow Mike Bostock's process to project to the bounding box of a feature in a topojson file. My topojson file is already projected to Texas State Mapping System (EPSG 3081) from the command line using geoproject:

d3.geoConicConformal().parallels([34 + 55 / 60, 27 + 25 / 60]).rotate([100, -31 - 10 / 60])

However, copying his code exactly and modifying the relevant bits to match my data set results in the error "Uncaught TypeError: path.bounds is not a function" on this line:

var b = path.bounds(state),

Here is my complete code:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>JS Mapping Project</title>
        <script type="text/javascript" src="https://d3js.org/d3.v3.min.js"></script>
        <script src="https://d3js.org/d3-array.v1.min.js"></script>
        <script src="https://d3js.org/d3-geo.v1.min.js"></script>
        <script src="https://d3js.org/d3-geo-projection.v1.min.js"></script>
        <script src="https://d3js.org/topojson.v2.min.js"></script>
        <style type="text/css">
            body {
                background-color: #eee;
            }
            svg {
                background-color: #fff;
                border: 1px solid #000;
            }
        </style>
    </head>
    <body>
        <script type="text/javascript">
            //Width and height
            var w = 1000;
            var h = 850;

            var projection = d3.geoProjection( function(x, y) {
                return [x, y];
            });

            // Create a path generator.
            var path = d3.geo.path()
                .projection();

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

            //Load in GeoJSON data
            d3.json("data/topojson/boundary_quantize.json", function(error, json) {
                //Add error handling
                if (error) throw error;

                var states = topojson.feature(json, json.objects.state),
                    state = states.features.filter(function(d) { return d.properties.NAME === "Texas"; })[0];

                projection
                    .scale(1)
                    .translate([0, 0]);

                // Compute the bounds of a feature of interest, then derive scale & translate.
                var b = path.bounds(state),
                    s = .95 / Math.max((b[1][0] - b[0][0]) / w, (b[1][1] - b[0][1]) / h),
                    t = [(w - s * (b[1][0] + b[0][0])) / 2, (h - s * (b[1][1] + b[0][1])) / 2];             

                // Update the projection to use computed scale & translate.
                projection
                    .scale(s)
                    .translate(t);

                svg.append("path")
                    .attr("stroke"," #000")
                    .attr("stroke-width", "2")
                    .attr("d", path(topojson.mesh(json, json.objects.national)));

                svg.append("path")
                    .attr("stroke"," #000")
                    .attr("stroke-width", "1")
                    .attr("d", path(topojson.mesh(json, json.objects.state)));

                svg.append("path")
                    .attr("stroke"," #000")
                    .attr("stroke-width", "0.5")
                    .attr("d", path(topojson.mesh(json, json.objects.county)));

            });
        </script>
    </body>
</html>

A few things that I've discovered manipulating the code:

If I remove the projection by changing var path = d3.geo.path().projection(); to var path = d3.geo.path();, the error goes away (because the call to the broken code goes away) but the svg draw is broken:

var path = d3.geo.path();

If I change the path definition to var path = d3.geoPath();, suddenly the geometry draws correctly:

var path = d3.geoPath();

This won't work (and I don't know why geoPath() works in the first place) because then the rest of my calls to path fail.

As I was typing this, I realized that I forget the call to my projection variable. I changed .projection(); to .projection(projection);. Now my map looks really weird, but there aren't errors on the path.bounds line as before:

.projection(projection);

It would seem that my projection definition is wrong, despite using the formula from this StackExchange answer.

I changed the code from geoProjection() to geoIdentity() based on Mike Bostock's response to a comment on his Medium article. My map appeared to be projected, scaled, and centered correctly, but the all black and white color scheme didn't help. I added some quick coloring to the various layers and it now looks very broken:

Correctly projected, scaled, and centered, but broken features.

I then thought maybe it was because I didn't add the ", function(a,b) { return a !== b; }" to the topojson.mesh function, but doing that made things more broken:

Filtering for a!==b

I double-checked my topojson file again at mapshaper.org, but my geometry is correct:

Geometry check at mapshaper.org

At this point I'm stumped. Something is wrong with the way I'm implementing topojson to render my data, but it matches the code I'm seeing in examples.

1

1 Answers

3
votes

Had a breakthrough here. By changing the call to the feature from topojson.mesh to topojson.feature, the problem instantly resolved itself. I don't know why .mesh works in this example, but it definitely didn't work for me.

enter image description here

EDIT: I've identified why .mesh has been used in the example. It is to select only the internal boundaries so the coastline is not rendered, which is a great idea from a cartographic perspective. The thing I realized is to not apply a fill to those paths to prevent the drawing errors I had before.