D3.geo.orthographic Drawing Circles
This chapter looks at D3'southward approach to rendering geographic information.
As an example, the globe below is drawn using D3. A GeoJSON file is loaded and D3 is used to project the geographic data and draw it on a Canvas element:
D3's approach differs to and then called raster methods such every bit Leaflet and Google Maps. These pre-render map features as epitome tiles and these are loaded from a web server and pieced together in the browser to course a map.
Typically D3 requests vector geographic information in the grade of GeoJSON and renders this to SVG or Sheet in the browser.
Raster maps often expect more than similar traditional print maps where a lot of item (e.1000. place names, roads, rivers etc.) tin can be shown without an impact on functioning. Even so, dynamic content such as animation and interaction is more easily implemented using a vector arroyo. (It's also quite common to combine the two approaches.)
D3 mapping concepts
The three concepts that are key to understanding map creation using D3 are:
- GeoJSON (a JSON-based format for specifying geographic data)
- projections (functions that convert from latitude/longitude co-ordinates to ten & y co-ordinates)
- geographic path generators (functions that convert GeoJSON shapes into SVG or Canvass paths)
GeoJSON
GeoJSON is a standard for representing geographic information using the JSON format and the full specification is at geojson.org.
Here's a typical GeoJSON object:
{ "type" : "FeatureCollection" , "features" : [ { "blazon" : "Characteristic" , "properties" : { "proper noun" : "Africa" }, "geometry" : { "type" : "Polygon" , "coordinates" : [[[ -half-dozen , 36 ], [ 33 , xxx ], ... , [ -6 , 36 ]]] } }, { "type" : "Feature" , "properties" : { "name" : "Commonwealth of australia" }, "geometry" : { "type" : "Polygon" , "coordinates" : [[[ 143 , -eleven ], [ 153 , -28 ], ... , [ 143 , -xi ]]] } }, { "type" : "Characteristic" , "backdrop" : { "name" : "Timbuktu" }, "geometry" : { "type" : "Bespeak" , "coordinates" : [ -3.0026 , 16.7666 ] } } ] } In the above object in that location'southward a FeatureCollection containing an assortment of 3 features:
- Africa
- Australia
- the metropolis of Timbuktu
Each feature consists of geometry (uncomplicated polygons in the instance of the countries and a bespeak for Timbuktu) and backdrop.
Backdrop can contain any information virtually the feature such as name, id, and other data such equally population, Gdp etc.
D3 takes care of well-nigh of the item when rendering GeoJSON so you only demand a basic understanding of GeoJSON to become started with D3 mapping.
Projections
A projection role takes a longitude and latitude according (in the course of an array [lon, lat]) and transforms it into an x and y co-ordinate:
role projection ( lonLat ) { permit x = ... // some formula here to summate ten let y = ... // some formula here to summate y render [ x , y ]; } projection ( [ - 3.0026 , 16.7666 ] ) // returns [474.7594743879618, 220.7367625635119] Project mathematics can get quite complex just fortunately D3 provides a large number of projection functions.
For instance y'all can create an equi-rectangular projection function using:
let projection = d3 . geoEquirectangular (); projection ( [ - 3.0026 , 16.7666 ] ) // returns [474.7594743879618, 220.7367625635119] We'll look at projections in more than detail later.
Geographic path generators
A geographic path generator is a function that takes a GeoJSON object and converts it into an SVG path string. (In fact, it's just another type of shape generator.)
You can create a generator using the method .geoPath and configure it with a project function:
let project = d3 . geoEquirectangular (); let geoGenerator = d3 . geoPath () . project ( projection ); permit geoJson = { " blazon " : " Feature " , " properties " : { " proper noun " : " Africa " }, " geometry " : { " type " : " Polygon " , " coordinates " : [[[ - 6 , 36 ], [ 33 , 30 ], ... , [ - vi , 36 ]]] } } geoGenerator ( geoJson ); // returns "M464.0166237760863,154.09974265651798L491.1506253268278,154.8895088551978 ... L448.03311471280136,183.1346693994119Z" As usual with shape generators the generated path string is used to gear up the d aspect on an SVG path chemical element.
Putting it all together
Given some GeoJSON, a projection function and a geographic path generator you can create a basic map:
let geoJson = { " blazon " : " FeatureCollection " , " features " : [ { " type " : " Feature " , " properties " : { " name " : " Africa " }, " geometry " : { " type " : " Polygon " , " coordinates " : [[[ - 6 , 36 ], [ 33 , xxx ], ... , [ - 6 , 36 ]]] } }, ... ] } let project = d3 . geoEquirectangular (); let geoGenerator = d3 . geoPath () . projection ( projection ); // Join the FeatureCollection's features array to path elements let u = d3 . select ( ' #content thou.map ' ) . selectAll ( ' path ' ) . data ( geojson . features ) . bring together ( ' path ' ) . attr ( ' d ' , geoGenerator ); geoJson.features is an array of features. This assortment is joined to path elements. The d aspect is gear up using the function geoGenerator. This receives a feature equally its first parameter and outputs a path string.
The last line may look similar magic only is the equivalent of:
.attr('d', part(d) { render geoGenerator(d); }); In this case the parameter d is a GeoJSON characteristic.
To keep things elementary the GeoJSON in the in a higher place example uses simply a few co-ordinates to define the country boundaries.
The above example shows the essence of creating maps using D3 and I recommend spending time to understand each concept (GeoJSON, projections and geo generators) and how they fit together.
Now that we've covered the basics nosotros'll look at each concept in more detail.
GeoJSON
GeoJSON is a JSON-based construction for specifying geographic data. By and large it'southward converted from shapefile data (a geospatial vector data format widely used in the GIS field) using tools such as mapshaper, ogr2ogr, shp2json or QGIS.
A pop source of world map shapefiles is Natural World and if starting out I recommend trying out mapshaper for importing shapefiles and exporting as GeoJSON. It can also filter past backdrop (due east.g. if you wanted to filter countries by continent). For a more in depth look at conversion look at Mike Bostock's Let'southward Make a Map tutorial.
Yous tin create maps without understanding the GeoJSON specification in minute detail because tools such every bit mapshaper and D3 do such a good job of abstracting away the detail. Still, if y'all did want to empathise GeoJSON in greater depth I recommend checking out the official specification.
So far we've embedded a GeoJSON object in our example files. In practise the GeoJSON would be in a separate file and loaded using an ajax asking. We encompass requests in more detail in the requests chapter merely for the remainder of this chapter we'll load a GeoJSON file using:
d3 . json ( ' ne_110m_land.json ' , function ( err , json ) { createMap ( json ); }) Information technology'south worth mentioning TopoJSON which is another JSON based standard for describing geographic information and tends to consequence in significantly smaller file sizes. It requires a fleck more work to use, and we don't cover it in this chapter. However for farther information cheque out the documentation.
Projections
At that place are numerous (if non infinite) ways of converting (or 'projecting') a point on a sphere (e.m. the earth) to a point on a flat surface (e.grand. a screen) and people accept written countless articles (such equally this one) on the pros and cons of unlike projections.
In brusque there is no perfect projection as every projection will distort shape, expanse, distance and/or direction. Choosing a project is a case of choosing which property you lot don't want to be distorted and accepting that there'll be distortion in the other properties (or cull a projection that strives for a balanced approach). For example, if it's important that the size of countries are represented accurately and so choose a project that strives to preserve area (probably to the toll of shape, altitude and management).
D3 has a number of core projections that should comprehend about use cases:
-
geoAzimuthalEqualArea -
geoAzimuthalEquidistant -
geoGnomonic -
geoOrthographic -
geoStereographic -
geoAlbers -
geoConicConformal -
geoConicEqualArea -
geoConicEquidistant -
geoEquirectangular -
geoMercator -
geoTransverseMercator
Some projections preserve expanse (east.1000. geoAzimuthalEqualArea & geoConicEqualArea), others distance (e.g. geoAzimuthalEquidistant & geoConicEquidistant) and others relative angles (eastward.chiliad. geoEquirectangular & geoMercator). For a more than in depth give-and-take of the pros and cons of each projection attempt resource such as Carlos A. Furuti's Map Projection Pages.
The filigree below shows each core projection on a earth map together with a longitude/latitude filigree and equal radius circles.
Projection functions
A project function takes input [longitude, breadth] and outputs a pixel co-ordinate [x, y].
Be careful to note the order of longitude and latitude in the in a higher place array!
Yous're free to write your ain projection functions merely much easier is to ask D3 to brand one for you. To do this choose a projection method (due east.chiliad. d3.geoAzimuthalEqualArea), telephone call it and it'll render a projection function:
let projection = d3 . geoAzimuthalEqualArea (); projection ( [ - 3.0026 , xvi.7666 ] ); // returns [473.67353385539417, 213.6120079887163] The cadre projections have configuration functions for setting the following parameters:
| scale | Scale cistron of the projection |
| heart | Projection center [longitude, latitude] |
| interpret | Pixel [10,y] location of the projection centre |
| rotate | Rotation of the projection [lambda, phi, gamma] (or [yaw, pitch, roll]) |
The precise significant of each parameter is dependent on the mathematics behind each projection simply broadly speaking:
- scale specifies the calibration cistron of the projection. The higher the number the larger the map.
- center specifies the center of projection (with a
[lon, lat]array) - interpret specifies where the centre of projection is located on the screen (with a
[x, y]array) - rotate specifies the rotation of the project (with a
[λ, φ, γ]array) where the parameters correspond to yaw, pitch and gyre, respectively:
For example you can create and configure a projection function such that Timbuktu is centred in a 960x500 map using:
let projection = d3 . geoAzimuthalEqualArea () . scale ( 300 ) . center ([ - 3.0026 , xvi.7666 ]) . translate ([ 480 , 250 ]); To get a experience for how each parameter behaves utilise the projection explorer below. The (equal radius) circles and grid allow you to appraise the projection's distortion of area and angle.
.invert()
You can convert a pixel co-ordinate [x, y] to a longitude/breadth array using the projection's .capsize() method:
permit project = d3 . geoAzimuthalEqualArea (); projection ( [ - 3.0026 , xvi.7666 ] ) // returns [473.67353385539417, 213.6120079887163] projection . capsize ( [ 473.67353385539417 , 213.6120079887163 ] ) // returns [-3.0026, xvi.766] Fitting
Given a GeoJSON object, a projection's .fitExtent() method sets the projection's calibration and translate such that the geometry fits within a given bounding box:
project . fitExtent ([[ 0 , 0 ], [ 900 , 500 ]], geojson ); The commencement argument of .fitExtent is an array containing ii coordinates: the top left point ([x, y]) of the bounding box and the size ([width, height]) of the bounding box. The second argument is a GeoJSON object.
In the example beneath the sheet element has a light grey background and the bounding box into which we're fitting the geoJSON is shown equally a dotted outline. The following code is used to fit the geometry within the bounding box:
projection . fitExtent ([[ 20 , twenty ], [ 620 , 420 ]], geojson ); If your bounding box'due south top left corner is at [0, 0] you can omit the top left coordinate and simply supply the width and top:
projection . fitSize ([ 900 , 500 ], geojson ); Geographic path generators
A geographic path generator is a office that transforms GeoJSON into an SVG path string (or into canvas element calls):
geoGenerator ( geoJson ); // e.chiliad. returns a SVG path string "M464.01,154.09L491.fifteen,154.88 ... L448.03,183.13Z" You create the generator using d3.geoPath() and must configure information technology's projection type:
let projection = d3 . geoEquirectangular (); permit geoGenerator = d3 . geoPath () . projection ( projection ); You tin now use the generator to assist create an SVG or canvass map. The SVG option is a bit easier to implement, especially when it comes to user interaction (because consequence handlers and hover states tin can be added).
The canvas approach requires a flake more piece of work but is typically faster to render (and more retentivity efficient).
Rendering SVG
To return an SVG map yous:
- join a GeoJSON features array to SVG
pathelements - update each
pathelement'sdaspect using the geographic path generator
For example:
let geoJson = { " type " : " FeatureCollection " , " features " : [ { " type " : " Feature " , " properties " : { " name " : " Africa " }, " geometry " : { " type " : " Polygon " , " coordinates " : [[[ - half-dozen , 36 ], [ 33 , 30 ], ... , [ - 6 , 36 ]]] } }, { " type " : " Feature " , " properties " : { " name " : " Australia " }, " geometry " : { " type " : " Polygon " , " coordinates " : [[[ 143 , - 11 ], [ 153 , - 28 ], ... , [ 143 , - eleven ]]] } }, { " blazon " : " Feature " , " properties " : { " name " : " Timbuktu " }, " geometry " : { " blazon " : " Point " , " coordinates " : [ - three.0026 , xvi.7666 ] } } ] } let projection = d3 . geoEquirectangular (); let geoGenerator = d3 . geoPath () . projection ( projection ); // Join the FeatureCollection'southward features array to path elements let u = d3 . select ( ' #content g.map ' ) . selectAll ( ' path ' ) . data ( geojson . features ) . join ( ' path ' ) . attr ( ' d ' , geoGenerator ); geoJson.features is an array of features. This array is joined to path elements. The d aspect is set using the part geoGenerator. This receives a feature as its first parameter and outputs a path string.
Rendering to canvas
To render to a canvas chemical element you laissez passer the canvas DOM element into the generator'due south context method:
permit context = d3 . select ( ' #content sheet ' ) . node () . getContext ( ' 2d ' ); let geoGenerator = d3 . geoPath () . project ( project ) . context ( context ); The
.nodemethod returns the first DOM chemical element of a selection.
You and then begin a canvass path (using context.beginPath()) and call geoGenerator which will produce the necessary canvas calls:
context . beginPath (); geoGenerator ({ type : ' FeatureCollection ' , features : geojson . features }) context . stroke (); Lines and arcs
The geographic path generator is clever plenty to distinguish betwixt polygonal (typically for geographic areas) and point (typically for lon/lat locations) features. As can be seen in the above examples information technology renders polygons as line segments and points as arcs.
Y'all can ready the radius of the circles using .pointRadius():
let geoGenerator = d3 . geoPath () . pointRadius ( 5 ) . projection ( projection ); Path geometry
The geographic path generator can as well be used to compute the area (in pixels), centroid, bounding box and path length (in pixels) of a projected GeoJSON feature:
let feature = geojson . features [ 0 ]; // Compute the feature's expanse (in pixels) geoGenerator . surface area ( characteristic ); // returns 30324.86518469876 // Compute the feature's centroid (in pixel co-ordinates) geoGenerator . centroid ( feature ); // returns [266.9510120424504, 127.35819206325564] // Compute the feature's bounds (in pixel co-ordinates) geoGenerator . bounds ( feature ); // returns [[140.6588054321928, 24.336293856408275], [378.02358370342165, 272.17304763960306]] // Compute the path length (in pixels) geoGenerator . measure ( characteristic ); // returns 775.7895349902461 This example shows the area and length of a hovered path. It also draws the path's centroid and bounding box:
Shapes
If you need to add lines and/or circles to a map you lot can add together features to the GeoJSON.
Lines can exist added equally a LineString feature and will be projected into great-arcs (i.e. the shortest distance beyond the surface of the globe).
Here's an example where a line is added betwixt London and New York:
geoGenerator ({ blazon : ' Characteristic ' , geometry : { type : ' LineString ' , coordinates : [[ 0.1278 , 51.5074 ], [ - 74.0059 , twoscore.7128 ]] } }); Circle features tin can be generated using d3.geoCircle(). This creates a circle generator which returns a GeoJSON object representing a circumvolve.
Typically the eye ([lon, lat]) and the radius (in degrees) are set:
let circleGenerator = d3 . geoCircle () . center ([ 0.1278 , 51.5074 ]) . radius ( 5 ); permit circle = circleGenerator (); // returns a GeoJSON object representing a circumvolve geoGenerator ( circle ); // returns a path string representing the projected circle A GeoJSON filigree of longitude and latitude lines (known every bit a graticule) can be generated using d3.graticule(). This creates a graticule generator which returns a GeoJSON object representing the graticules:
let graticuleGenerator = d3 . geoGraticule (); let graticules = graticuleGenerator (); // returns a GeoJSON object representing the graticule geoGenerator ( graticules ); // returns a path string representing the projected graticule (See the official documentation for detailed information on graticule configuration.)
Hither'due south an example where a line, a circumvolve and graticules are added to a map:
Spherical geometry
There's a scattering of D3 methods that may come up in useful from fourth dimension to time. The first of these .geoArea(), .geoBounds(), .geoCentroid(), .geoDistance() and geoLength() are like to the path geometry methods described above but operate in spherical space.
Interpolation
The d3.geoInterpolate() method creates a function that accepts input between 0 and ane and interpolates between two [lon, lat] locations:
let londonLonLat = [ 0.1278 , 51.5074 ]; let newYorkLonLat = [ - 74.0059 , 40.7128 ]; allow geoInterpolator = d3 . geoInterpolate ( londonLonLat , newYorkLonLat ); geoInterpolator ( 0 ); // returns [0.1278, 51.5074] geoInterpolator ( 0.v ); // returns [-41.182023242967695, 52.41428456719971] (halfway between the two locations) geoContains
If yous're using a canvass element to render your geometry you don't accept the luxury of being able to add event handlers onto SVG path elements. Instead y'all can check whether mouse or touch events occur inside the boundary of a feature. You can do this using d3.geoContains which accepts a GeoJSON feature and a [lon, lat] assortment and returns a boolean:
d3 . geoContains ( ukFeature , [ 0.1278 , 51.5074 ]); // returns true Source: https://www.d3indepth.com/geographic/
Post a Comment for "D3.geo.orthographic Drawing Circles"