Setting a custom projection using d3-geo-projectio

2019-08-04 19:40发布

问题:

I'm trying to use geoproject to set the projection on a geojson file. Specifically I'm trying to set the projection to BCalbers (http://spatialreference.org/ref/epsg/3005/)

I see geoproject has a number of projections options i.e.

geoproject 'd3.geoAlbersUsa()' us.json \
  > us-albers.json

but it is possible to set a custom projection using the command line tool? I was hoping something like this would be possible:

geoproject '+proj=aea +lat_1=50 +lat_2=58.5 +lat_0=45 +lon_0=-126 +x_0=1000000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs' build/airzones.geojson \
  > bc-albers.json

but no dice. This works with ogr2ogr

ogr2ogr -f GeoJSON -t_srs "+proj=aea +lat_1=50 +lat_2=58.5 +lat_0=45 +lon_0=-126 +x_0=1000000 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs  " \

回答1:

D3 geo projections support a range of generic projections (and custom projections), recreating any given specific projection is generally possible. However, when re-creating a projection, d3 projections don't replicate map units*. D3 projections create units in svg coordinate space (the projected coordinates will start from [0,0] which is the top left corner). This allows skipping the step of projecting data (often on the fly with d3), and then rescaling and translating it to show a map.

Compare: Using the referenced d3.geoAlbersUsa(), d3 will project data across roughly 960 pixels along the x axis if using the default scale. Perfect for unmodified use in an SVG - coordinates could be used as straight SVG coordinates. Using a BC Albers in ArcGIS or QGIS will project data across millions of meters.

Units aside, however, you can recreate a BC Albers proportionally scaled for web preserving the shape, distance, direction, and area of a regular BC Albers. But as d3 doesn't take projection definitions such as .prj files or other definitions, you need to use a d3's projection methods and the appropriate parameters.

For a BC Albers your parallels are: 50 and 58.5, your central longitude is -126, and your projection type is an Albers. This is all you need - the (false) easting/northing reference is to recreate map units - which should not generally be needed in a web scenario (if you do, using a more complete GIS platform would be more appropriate).

So, to set the projection, you would use:

d3.geoAlbers()
  .center([0,y])
  .rotate([-x,0])
  .parallels([a,b])
  .scale(k)

Where

x = center longitude (negative because we rotate the globe under the map)

a, b = standard parallels

k = scale factor(for a d3.geoAlbers(): whole world is 960 px across with a scale of 1070, the default scale, larger numbers expand this/zoom in)

y = centering latitude.

Note: y does not alter the projection, it merely translates it - the y reference for a BC Albers is south of BC, it is just a reference for northings, as it has no impact on map shape, area, distance, or direction. For a BC Albers, I would probably choose 50.5 as it is half way between the Yukon border and the Washington border which are the northern and southern limits of BC (well, excepting Vancouver Island and some of the Gulf Islands, so let's say 50 degrees north, sorry I forgot about you Victoria).

Also remember the the projection functionality of d3 assumes data that is unprojected (or "projected" in WGS84), consisting of long lat pairs.

You can see how Mike Bostock uses these methods in his command line cartography article here:

geoproject 'd3.geoConicEqualArea().parallels([34, 40.5]).rotate([120, 0]).fitSize([960, 960], d)' < ca.json > ca-albers.json

fitSize in this exmaple scales and translates the features to the specified bounding box - this translate and scale does not alter the projection parameters, and like the y coordinate in the center method, does not alter distance, area, shape, or angle (well, distance and area remain proportional to a proper BC Albers).

*You could recreate map units (false eastings/northings might require some custom projection work), but this is not the platform for it really, it would be easier to use many other platforms.

See also this question and answer:Converting EPSG projection bounds to a D3.js.