How to Add or Create a Map Scale Bar to a Map created with D3.js

I am seeking help with creating a map scale bar for use with a D3.js zoomable map. I often create maps for my job using D3.js. I never placed a scale bar on the maps I built in the past but I recently gotten requests to include a scale bar on the maps. I searched the web for some type of example to build off of, but I couldn't find anything. I toyed around with d3.scale.linear() but I'm unable to create anything useful.

What I'm trying to create is a map scale bar that adjust it's numbers accordingly when zooming in and out with d3.behavior.zoom(). Any help with this issue (a working model or something to build off of) will greatly be appreciated. Thank you in advance.

Answers:

Answer

I made a D3.js plugin called d3-geo-scale-bar that should accomplish what you're looking for.

Here's the GitHub repo: https://github.com/HarryStevens/d3-geo-scale-bar

Here's a basic demo: https://bl.ocks.org/HarryStevens/8c8d3a489aa1372e14b8084f94b32464

And here's a demo with pan and zoom: https://bl.ocks.org/HarryStevens/64fc5f1a4489abe78433b7d19510f864

Here's an Observable notebook demo: https://observablehq.com/d/75249011244aafd6

Answer

There are several issues you need to address in a web scale bar:

  • Map distances will vary across a projection
  • D3 projections project lat long data, which can't directly be used to measure physical distance
  • Physical distance must be measured using great circle distance

I designed a scale for a map a while back that used the formula here for physical distance between to geographic coordinates. But what two points do you use to measure distance? I used two points that when connected formed a line through the center of the map frame (horizontally and not to the edges for a landscape oriented map) the scale near the center of the map is likely most representative of the map scale as a whole. To get the geographic location of a point using svg coordinates, use projection.invert([x,y]) to get your longitude and latitude.

With the two points, you should then be able to calculate physical distance and svg coordinate distance, giving you the information needed for a scale. This might look like:

var scaleWidth = width / 4;
var p1 = projection.invert([width/2 - scaleWidth/2, height / 2]);
var p2 = projection.invert([width/2 + scaleWidth/2, height / 2]);
var geoDistance = getDistance(p1,p2);

Where getDistance is a function that returns the great circle distance between two geographic points, and the geographic distance this function returns is represented by width/4 pixels on the map. Now, for the simplest scale, you can draw a rectangle width/4 pixels wide and label it as being the distance returned by getDistance long.

Note that if you choose the same two svg points every time as your reference points for the scale, the scale bar may show a un-round length. So, you'll need to modify the distance until you get a sufficiently round number, and then draw the scale bar in a corner (unless a scale bar 1.954 km wide is ok, this will be faster too).

To zoom, just redraw (and recalculate the distance between two central points, zoom factor will not help you in this) and/or transition the scale to represent the new physical distance between the reference points (recalculating them as needed to make a round number).

Here's a bl.ock of the scale in action, and an image of it below:

enter image description here

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.