1

Background

I'm building a map that displays a map of the US. On this map I am plotting heat circles that correspond to a Nielsen DMA topography.

The first topojson that I'm using, is this Nielsen DMA topojson (from simzhou's repo here) to visually plot these heat circles across the US map.

Below you can see the map, with the DMA heat circles, and also the DMA border lines built completely from the Nielsen DMA topojson.

screen shot 2018-05-31 at 4 37 19 pm

Problem:

The issue I'm having is trying to draw state border lines, instead of these DMA border lines. I've brought in the "https://unpkg.com/us-atlas@1/us/10m.json" that @mbostock has provided for us. When drawing the state borders via topojson.feature(us, us.objects.states).features (i've tried topojson.mesh too), that's when things go awry. I am 99% sure that this is because the two json files are using different transform values, and therefore the positions/coordinates are transformed on different scales.

Here are the two jsons: Nielsen DMA here and the US here

You can see how the transform object differs below:

Transform object from US Atlas

"transform": {
       "scale": [0.009995801851947097,0.005844667153098606],
       "translate":[-56.77775821661018,12.469025989284091]
}

Transform object from DMA topojson

"transform": {
        "scale": [0.00577894299429943, 0.002484260626062607],
        "translate": [-124.732975, 24.544237]
},

Here's what I've done so far.

Approach:

1. Round trip through GeoJSON

As detailed by Bostock here I've tried making a new topoJSON "via a round trip through GeoJSON."

Quantized → non-quantized, to remove quantization. This is often done temporarily to process data (for example, topojson.presimplify). I suppose you might want this so that you could combine topologies with different quantized transforms, but you could always do this by making a round trip through GeoJSON.

For each of the jsons 1. I converted them from topoJSON to geoJSON. topo2geo nielsen_dma=us-dma-geo.json < us-dma-topo.json Now for each json we have a feature collection with absolute coordinates. 2. With the new geoJSON, I then converted them back to topoJSON via CLI. geo2topo nielsen_dma=us-dma-geo.json > us-dma-topo.json Both JSONs no longer have the transform property, but they do have bbox. 3. Now I have both jsons make a round trip from topojson -> geojson -> topojson. 4. I stripped bbox properties from both jsons, as they are optional. 5. I then simply added over the geometry collection of one to the other. statesJSON.objects.nielsen_dma = dmaJSON.objects.nielsen_dma

I now have a topojson with the nielsen_dma and states geometries. However this still doesn't work, and drawing the state lines brings chaos.

screen shot 2018-05-31 at 5 18 13 pm

Did I fail to remove the quantization of the coordinates for both jsons during the round trip to geoJSON?

Possibly (ir)relevant Question:

  1. The Nielsen DMA map does not include geometries for Alaska and Hawaii. Could this discrepancy between the two jsons lead to this issue?
jaysonder
  • 377
  • 4
  • 12
  • 1
    If you convert your file to geojson you can see the coordinates - they aren't latitude and longitude pairs. You need longitude and latitude pairs to be able to project data with a d3 projection. This US topojson is preprojected for use with a null projection (each coordinate represents a pixel, not a point on a 3d globe). This [answer](https://stackoverflow.com/questions/42430361/scaling-d3-v4-map-to-fit-svg-or-at-all/42430876#42430876) may help. The easiest solution is to source a geojson/topojson of the US from somewhere like [here](https://github.com/jgoodall/us-maps). – Andrew Reid May 31 '18 at 21:38
  • @AndrewReid I appreciate the direction with the comment and the links. I'm going to dive into this over the next week and get this figured out. Thanks! – jaysonder Jun 08 '18 at 17:43
  • Hey @AndrewReid do you have any resources to point to for 1. Is it possible to revert a topoJSON back to a shapefile? 2. How do the transform values relate from one topojson to another? Are those transform values created from the pre-projection when making the topojson? – jaysonder Jun 11 '18 at 20:58
  • What I'm trying to do is normalize the cartesian coordinates of both topojsons here, so that when mapping out the lines of each topojson, they lay perfectly one on top of another. – jaysonder Jun 11 '18 at 20:59
  • 1
    Mapshaper.org can convert between shape/topojson/geojson. The transform values of the topojson are only for compression of the underlying coordinates, they do not modify the projection (or lack of), as such you cannot modify the topojson directly so that the unprojected lat long (non-Cartesian) coordinates of one layer match the projected Cartesian coordinates of the other. – Andrew Reid Jun 11 '18 at 21:07
  • 1
    I apologize in that the referenced geojson in my first comment was a bit big (you can use mapshaper to simplify it to a more appropriate size for web browsers). But using this is vastly preferable - otherwise you are working with two different coordinate systems (which differ by more than translate and scale - one is a projected 2d space (using an Albers projection), the other consists of points on a 3d globe (and if plainly transcribed to Cartesian coordinate space is a Plate Caree projection). Using all lat long or all projected x,y allows for easier manipulation ensures overlaps of features – Andrew Reid Jun 11 '18 at 21:21
  • Using the linked geojson data (converted to topojson and simplified on mapshaper.org) and your nielsen topojson I get: https://bl.ocks.org/andrew-reid/178445c6acd84aa3b43525076f277157 (oh, just noticed that the geojson apparently omits some states, let me find a better one, sigh) – Andrew Reid Jun 11 '18 at 21:28
  • 1
    (the original linked dataset does have all states, but for some reason somewhere along the line in my copying and pasting I lost a few, but by the time I realized this, I had already sourced out a smaller geojson and converted it to topojson for the block: https://github.com/PublicaMundi/MappingAPI/blob/master/data/geojson/us-states.json). – Andrew Reid Jun 11 '18 at 21:36

1 Answers1

1

When I first read this I attempted to address the underlying problem in the comments (for which I missed the mark a bit, as I assumed the code to display the map looked a little different than it likely was - I apologize for the misread). However, on second examination, I realized that while the question is an x,y problem, the attempted solution leads to some interesting questions about D3 and topojson that aren't immediately clear - but the underlying issue remains mixing projected and unprojected data, so I'll attempt to address both the x and the y here:

Topojson

Topojson essentially is a method of storing geojson features or feature classes by encoding topology and, optionally, quantized delta encoded integer coordinates. Topojson itself doesn't change the underlying coordinates, it changes their representation.

In order to use topojson with D3, we need to convert it back to geojson, as D3 geoPaths only accept geojson - topojson.feature() returns geojson.

The Underlying Source of Pain

The ultimate issue doesn't arise from the topojson at all. You have two topojon sources that use different coordinate systems:

  • the US json uses two dimensional Cartesian coordinate space (features projected with an Albers projection). The uncompressed coordinates are SVG pixels.

  • the Nielsen json uses latitude/longitude coordinate space (points on a 3d globe). The uncompressed coordinates are not Cartesian (unless applying a straight Plate Carree projection: long = x, lat = y).

When the US json is passed through topojson.feature(), the returned geojson will have x,y values within a bounding box of roughly [0,0],[960,600] (in this case). When any unprojected topojson (Nielsen json for exmaple) is passed through topojson.feature(), the returned geojson will have long,lat values within a bounding box of [-180,90],[180,-90] - that is it will have valid long/lat pairs.

These underlying and differing coordinate systems are the issue, not the representation of the coordinates in the topojson.

Combining Topojson

I'm not sure on the details of how you combined the two. A topojson splits geojson into constituent parts - arcs and features (which reference the arcs and hold the feature properties). The arcs are referenced numerically - if you didn't modify the indexes, then you can't just use:

statesJSON.objects.nielsen_dma = dmaJSON.objects.nielsen_dma

But, even if you did modify the arc indexes and bring in the arcs that aren't in the statesJSON but are in the dmaJSON, you'd still run into problems. This is because we are still using different coordinate systems.

The transform that a topojson uses consists of scale and translate - the projected data uses an Albers projection, the unprojected data doesn't use any projection, but could be coerced to x,y with a Plate Carree projection (long=x,lat=y). But a Plate Carree features latitudes that are straight (it is an equirectangular projection), while the Albers features latitudes that are curved (it is a conical projection). Consequently, the shapes of identical features in each coordinate system will be different. Directions too will be different between each coordinate system (East is not to the direct right on most places on an Albers). You cannot modify shape and direction with translate and scale.

Different Topojson Transforms

I am 99% sure that this is because the two json files are using different transform values, and therefore the positions/coordinates are transformed on different scales.

It will be very rare to use two topojson files that have the same transform values, likewise with bounding box. The transform is only critical for reproducing the original coordinates that were used to create the topojson. Its values are only relevant for converting encoded topojson coordinates back to the original (geojson) coordinates. The transform is specific to that topojson's coordinates.

Ultimately, the transform is not relevant when using D3: the original coordinates used to create the topojson are what is actually used by D3, through topojson.feature(). The transform is essentially immaterial - as long as it is constant for the encoding and decoding.

How Do I Fix This?

You have a projected file (the US json) for which you do not know the projection that was used to create it. Without knowing the projection used to create it, you cannot apply the same projection on the Nielsen data. Even if you could run the Nielsen data through a d3 geoProjection so that it overlaid the US data (which uses a null projection) - you shouldn't. Different coordinate systems are cumbersome when zooming, panning, or setting things like centering coordinates - it requires separate code to handle unprojected and projected data, essentially duplicating code

This means that you need to find a new data source for the US. Luckily there are many online sources, for example here are two: excessive size for web or reasonable size for web. You can convert to topojson if you want with mapshaper.org (and simplify the file too).

With a geojson/topojson for the US that is unprojected (long/lat pairs), you now have two files that share the same coordinate system. The topojson transform will be different for each (if working with topojson for both), this is immaterial, what matters is that the underlying coordinates are in the same coordinate space, not that their quantized representation in the topojson uses the same transform.

Here's a basic example using an unprojected US states geojson (from the second linked geojson, converted to topojson):

https://bl.ocks.org/andrew-reid/178445c6acd84aa3b43525076f277157

Andrew Reid
  • 37,021
  • 7
  • 64
  • 83
  • Thank you for breaking this issue down with further granularity. The pre-projection applied to Bostock's US json indeed was the root of this issue. The solution was simple as you suggested, take an unprojected US states geoJSON (which I converted to topoJSON) and using the same path generator for both topoJSONs, I now have a perfect overlay of the two maps! I was just about to ask whether it was still possible to combine the two jsons (by adding one object to the others `objects` property, but your explanation about Arcs + Features constituting one topoJSON seems to disprove that. – jaysonder Jun 12 '18 at 22:31
  • 1
    There are ways you can combine two topojsons - such as with mapshaper (though I've never done this), though of course they still must share the underlying coordinate system. – Andrew Reid Jun 14 '18 at 01:25