0.2.1 • Published 3 years ago

leaflet-topography v0.2.1

Weekly downloads
13
License
GPL-3.0-or-later
Repository
github
Last release
3 years ago

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:

resultunitrange
elevationmeters relative to sea level-413 to 8,848 (Dead Sea to Mt Everest)
slopedegrees0 - 90 (flat to vertical cliff)
aspectdegrees in polar coordinates (0 due east increasing counterclockwise)0 - 360
resolution*metersless 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 using rasterFunction: "Slope_Degrees" on an esri-leaflet terrain layer, which blacks out as you zoom out.
    • topotype: slopeaspect with a continuous: 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 custom heightFunction on a per-layer basis inside the customization object. However, currently there is a bug that is not allowing the stringified function to be passed to the web workers when passed as a customization option. For this reason, I recommend defining your heightFunction in the configure 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 of customization, 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:

License

GPL-3.0 License

0.2.1

3 years ago

0.2.0

3 years ago

0.1.2-next.0

3 years ago

0.1.1-next.0

3 years ago

0.1.1

3 years ago

0.1.0

4 years ago