leaflet-topography
v0.2.1
Published
a set of tools for calculating and visualizing topography in leafletjs
Downloads
287
Maintainers
Readme
Leaflet-topography is a leaflet plugin which offers functions and layers for calculating and visuzalizing topographic data in a leaflet map. These tools are based on the Mapbox RGB Encoded DEM, which means you must use your mapbox access token to use these tools.
Why?
While other tools exist to calculate and visualize topography in leaflet, this package is designed to do so at lightning speed. Under the hood, leaflet-topography uses your mapbox token to fetch the Mapbox-RGB-Terrain tile associated with your latlng
, and it then performs calculations to return elevation, slope, and aspect for that location. The point's associated DEM tile is cached in the format and location of your choice. This means that further queries that fall in the same tile return topography data quickly, without the need for another network request. For a detailed explanation of how this works, you can read my article, "Slope and Aspect as a Function of LatLng in Leaflet"
Installation and Use
You can install leaflet-topography through npm:
npm i leaflet-topography
Or you can include the package in your HTML head
using unpkg:
<head>
<script src="leaflet-CDN-comes-first" type="text/javascript"></script>
<script src="https://unpkg.com/leaflet-topography" type="text/javascript"></script>
</head>
leaflet-topography will attach to the leaflet global L
, and L.Topography
will now be available for use. You can also import relevant tools directly:
import Topography, { getTopography, configure, TopoLayer } from 'leaflet-topography'
Tools:
This is leaflet-topography's central tool. This async function takes in an L.LatLng
object, and a semi-optional configuration object, and returns a promise which resolves to the result, which contains elevation, slope, and aspect data for that latlng
. You can use async / await
syntax, or .then
syntax:
import Topography from 'leaflet-topography'
const map = L.map('mapdiv', mapOptions));
const options = {
token: 'your_mapbox_access_token'
}
// async / await syntax
map.on('click', async (e) => {
const results = await Topography.getTopography(e.latlng, options);
});
// promise .then syntax
map.on('click', (e) => {
Topography.getTopography(e.latlng, options)
.then((results) => console.log(results));
});
Results are returned with the following units:
| result | unit | range |
|---------------|-----------------------------------|----------------------------------------------|
| elevation
| meters relative to sea level | -413 to 8,848 (Dead Sea to Mt Everest) |
| slope
| degrees | 0 - 90 (flat to vertical cliff) |
| aspect
| degrees in polar coordinates (0 due east increasing counterclockwise) | 0 - 360 |
| resolution
* | meters | less than 1 to greater than 20,000,000 |
(*Resolution is a metadata value describing roughly how large the area used to calculate slope and aspect is. Slope and aspect are calculated based on a latlng
's 4 neighboring pixels. Higher scale
values have smaller distances between pixels, so the neightbors are closer together, and the resolution is better. Higher spread
values mean skipping more pixels to choose a neighbor, which worsens resolution. Larger resolution in this context means the surface being measured is more "smoothed out".)
Options
You must pass an options as the second argument of getTopography
, or you can use the configure
function to preconfigure leaflet-topography.
Cacheing Tiles
The key feature of leaflet-topography that enables returning topography data for high volumes of points in the same area in a short time is its data-cacheing behavior. Note the loose use of the word 'cache' - it is really in-memory storage. The default behavior is to simply store the DEM tiles in an object, with the key being the tile name in the format X<X>Y<Y>Z<Z>
, and the value being the data, either as a Uint8ClampedArray
or an ImageBitmap
. By default, the tiles are stored in L.Topography._tileCache
. However, you have the option to define your own cacheing functions to store the tiles wherever you like. You can use the saveTile
and retrieveTile
options to do this. For example, if you wanted to store the tiles on the window
object instead (not recommended), you could do this:
import { configure } from 'leaflet-topography'
const mySaveFunction = (name, data) => { window.myTemporaryCache[name] = data }
const myRetrieveFunction = (name) => return window.myTemporaryCache[name]
configure({
safeTile: mySaveFunction,
retrieveTile: myRetrieveFunction
})
And now your tiles will be saved to and retrieved from the window.myTemporaryCache
object. There are many in-browser data storage options, and these functions can be adapted to work with the storage method of your choice.
The TopoLayer
constructor will build a new tile layer, derived from the Mapbox RGB Terrain tileset. Using web workers and RainbowVis.js, a TopoLayer
transforms the rgb DEM to visualize topographic features. All of the thumbnails above were generated with variations of a TopoLayer
. It takes a configuration object as the contructor's argument:
import { TopoLayer } from 'leaflet-topography'
const elevationLayer = new TopoLayer({
topotype: 'elevation',
token: 'your_mapbox_token'
customization: <customization_options>
});
elevationLayer.addTo(map)
Constructor Options
Customization Options
The optional customization
object allows you to customize the way colors are rendered. It takes the following options, all of which are optional:
Colors and Breakpoints Hints and Tips
There are countless combinations of colors, breakpoints, continuous, and breakAt0. Many uses cases are untested, so open an issue or PR if you run into problems. Here are a few tips to get nice results:
You may find it useful to preconfigure leaflet-topography ahead of time. You can use the configure
function to do so, which will eliminate the need to pass an options
argument to getTopography
, or to pass your token to the TopoLayer
constructor.
// Create a map
const map = L.map('mapDiv', mapOptions));
// Configure leaflet-topography
L.Topography.configure({
token: your_mapbox_token
});
// Use leaflet topography, no need to pass options
map.on(click, async e => {
const { elevation, slope, aspect } = await L.Topography.getTopography(e.latlng)
console.log(elevation, slope, aspect)
})
// Add a TopoLayer, no need to pass token
const elevationLayer = new TopoLayer({ topotype: 'elevation' })
preload
is a convenience function which takes in an aray of L.LatLngBounds
and saves all DEM tiles within those bounds to the cache. If you know you will be doing analysis in a certain area(s), preload
will perform all the data fetching ahead of time. You must call configure
with your token
or tilesUrl
before calling preload
:
import L from 'leaflet';
import { preload, configure } from 'leaflet-topography';
const map = L.map('mapdiv', mapOptions));
configure({
token: 'your_mapbox_access_token',
priority: 'storage'
});
const corner1 = L.latLng(40.712, -74.227);
const corner2 = L.latLng(40.774, -74.125);
const analysisArea1 = L.latLngBounds(corner1, corner2);
const corner3 = L.latLng(41.712, -72.227);
const corner4 = L.latLng(41.774, -72.125);
const analysisArea2 = L.latLngBounds(corner3, corner4);
preload([analysisArea1, analysisArea2]);
map.on('click', e => {
getTopography(e.latlng)
.then(results => console.log(results));
});
Be careful! Calling preload
on too large an area with a high scale
may cause your browser to try to fetch hundreds or even millions of tile images at once!
Limitations
TopoLayer
topotype: slope
does not consider distance betwen pixels when calculating and coloring slope. Lower zoom levels produce higher slope values, meaning the layer tends to "white out" as you zoom out, and "black out" as you zoom in. Interestingly, this is in contrast to usingrasterFunction: "Slope_Degrees"
on an esri-leaflet terrain layer, which blacks out as you zoom out.topotype: slopeaspect
with acontinuous: true
is very slow, as each pixel's color must be calculated across two gradients - one to interpolate between aspect colors, and another to interpolate between the resultant aspect color and the slope value. This goes against the philosophy of this plugin, and should probably not be used.Bug: When creating a
TopoLayer
, you can pass a customheightFunction
on a per-layer basis inside thecustomization
object. However, currently there is a bug that is not allowing the stringified function to be passed to the web workers when passed as acustomization
option. For this reason, I recommend defining yourheightFunction
in theconfigure
function:L.Topography.configure({ heightFunction: (R, G, B) => return some_function_of_R_G_B })
If you want to define the
heightFunction
for a specific TopoLayer only, you can pass it as a property ofcustomization
, but if must be stringified:const customTopo = new TopoLayer({ topotype: 'aspect', tilesUrl: "your_url_here", customization: { heightFunction ((R, G, B) => { return return some_function_of_R_G_B; }).toString() } })
Planned Improvements
- Fix aforementioned
TopoLayer
bug - Units option for
getTopography
? - Fade-in effect on
TopoLayer
tiles when imageData is posted - Incorporate zoom level into
TopoLayer({ topotype: slope })
for consistent visuals across zoom levels - Smoothen
TopoLayer
at higher levels - General colorization algorithm optimization
Alternatives
Esri-leaflet can be used for both querying and visualizing topographic data in leaflet with relative ease. You can see some examples of how to do this in my articles Slope and Aspect as a function of LatLng
and Visualizing Topography. Leaflet-topography grew out of my dissatisfaction esri-leaflet, as I was in need of a way to query hundrends of points in the same area for topographic data in a very short time (on the order of seconds).
There are many tile layers and image layers which visualize slope, aspect, hillshade, and elevation, and you are likely to find a pre-fab layer that suits your needs. I wanted to have full control over customizing the coloration of my layers, which is what inspired TopoLayer
.
Further Reading
If you are interesting in nerding out on this as hard as me, here are some interesting articles about topography and hillshading, also in a mapbox context:
- Hillshade, by Sahil Chinoy
- DIY Hillshade, by Andy Woodruff
- Mapbox hillshade and satellite map blending, by Armand Emamdjomeh
License
GPL-3.0 License