SkillAgentSearch skills...

Leaflet.DistortableImage

A Leaflet extension to distort or "rubber sheet" images

Install / Use

/learn @publiclab/Leaflet.DistortableImage
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Leaflet.DistortableImage

Build Status Code of Conduct contributions welcome npm version

A Leaflet extension to distort images -- "rubbersheeting" -- for the MapKnitter.org (src) image georectification service by Public Lab. Leaflet.DistortableImage allows for perspectival distortions of images, client-side, using CSS3 transformations in the DOM.

Begin running (and contributing to) this codebase immediately with GitPod:

Open in Gitpod

Advantages include:

  • It can handle over 100 images smoothly, even on a smartphone
  • Images can be right-clicked and downloaded individually in their original state
  • CSS3 transforms are GPU-accelerated in most (all?) browsers, for a very smooth UI
  • No need to server-side generate raster GeoTiffs, tilesets, etc. in order to view distorted imagery layers
  • Images use DOM event handling for real-time distortion
  • Full resolution download option for large images, using WebGL acceleration

Download as zip or clone the repo to get a local copy.

Also available on NPM as leaflet-distortableimage:

npm i leaflet-distortableimage

Compatibility with Leaflet versions

Compatible with Leaflet 1.0.0 and greater

MapKnitter Lite

Read more about MapKnitter going offline here: https://publiclab.org/mapknitter

Check out a prototype of the Mapknitter Lite project which enables you to:

  • create maps and stitch images
  • download maps to save them
  • store saved maps and image collections in the Internet Archive (archive.org)
  • host maps at archive.org for others to see

Demo

Check out this simple demo.

And watch this GIF demo:

demo gif

To test the code, open index.html in your browser and click and drag the markers on the edges of the image. The image will show perspectival distortions.

For the additional features in the multiple image interface, open select.html and use <kbd>shift</kbd> + click on an image or <kbd>shift</kbd> + drag on the map to "multi-select" (collect) images. For touch screens, touch + hold the image.

Single Image Interface

The simplest implementation is to create a map with our recommended TileLayer, then create an L.distortableImageOverlay instance and add it onto the map.

// set the initial map center and zoom level
map = L.map('map').setView([51.505, -0.09], 13);

// adds a Google Satellite layer with a toner label overlay
map.addGoogleMutant();

map.whenReady(function() {
  // By default, 'img' will be placed centered on the map view specified above
  img = L.distortableImageOverlay('example.jpg').addTo(map);
});

<b>Note</b>: <code>map.addGoogleMutant()</code> is a convenience function for adding our recommended layer to the map. If you want a different baselayer, skip this line and add your preferred setup instead.

Options available to pass during L.DistortableImageOverlay initialization:

Actions

  • actions (optional, default: [L.DragAction, L.ScaleAction, L.DistortAction, L.RotateAction, L.FreeRotateAction, L.LockAction, L.OpacityAction, L.BorderAction, L.ExportAction, L.DeleteAction], value: array)

If you would like to overrwrite the default toolbar actions available for an individual image's L.Popup toolbar, pass an array with the actions you want. Reference the available values here.

For example, to overrwrite the toolbar to only include L.OpacityAction and L.DeleteAction , and also add on an additional non-default like L.RestoreAction:

img = L.distortableImageOverlay('example.jpg', {
  actions: [L.OpacityAction, L.DeleteAction, L.RestoreAction],
}).addTo(map);

Corners

  • corners (optional, default: an array of LatLangs that position the image on the center of the map, value: array)

Allows you to set an image's position on the map manually (somewhere other than the center default).

Note that this can manipulate the shape and dimensions of your image.

The corners should be passed as an array of L.latLng objects in NW, NE, SW, SE order (in a "Z" shape).

They will be stored on the image. See the Quick API Reference for their getter and setter methods.

Example:

img = L.distortableImageOverlay('example.jpg', {
  corners: [
    L.latLng(51.52,-0.14),
    L.latLng(51.52,-0.10),
    L.latLng(51.50,-0.14),
    L.latLng(51.50,-0.10),
  ],
}).addTo(map);

// you can grab the initial corner positions
JSON.stringify(img.getCorners())
=> "[{"lat":51.52,"lng":-0.14},{"lat":51.52,"lng":-0.1},{"lat":51.5,"lng":-0.14},{"lat":51.5,"lng":-0.1}]"

// ...move the image around...

// you can check the new corner positions.
JSON.stringify(img.getCorners())
=> "[{"lat":51.50685099607552,"lng":-0.06058305501937867},{"lat":51.50685099607552,"lng":-0.02058595418930054},{"lat":51.486652692081925,"lng":-0.06058305501937867},{"lat":51.486652692081925,"lng":-0.02058595418930054}]"

// note there is an added level of precision after dragging the image

Editable

editable (optional, default: true, value: boolean)

Internally, we use the image load event to trigger a call to img.editing.enable(), which sets up the editing interface (makes the image interactive, adds markers and toolbar).

If you want to enable editing based on custom logic instead, you can pass editable: false and then write your own function with a call to img.editing.enable(). Other passed options such as selected: true and mode will still be applicable and applied then.

<blockquote><b>Note</b>: when using the multiple image interface (<code>L.DistortableCollection</code>) this option will be ignored on individual <code>L.DistortableImageOverlay</code> instances and should instead be passed to the collection instance.</blockquote>

Full-resolution download

fullResolutionSrc (optional)

We've added a GPU-accelerated means to generate a full resolution version of the distorted image.

When instantiating a Distortable Image, pass in a fullResolutionSrc option set to the url of the higher resolution image. This image will be used in full-res exporting.

img = L.distortableImageOverlay('example.jpg', {
  fullResolutionSrc: 'large.jpg',
}).addTo(map);

Our project includes two additional dependencies to enable this feature, glfx.js and webgl-distort, both of which you can find in our package.json.

Mode

mode (optional, default: "distort", value: string)

This option sets the image's initial editing mode, meaning the corresponding editing handles will always appear first when you interact with the image.

Values available to pass to mode are:

  • distort (default): Distortion via individually draggable corners.
  • drag: Translation via individually draggable corners.
  • rotate: Rotation only.
  • scale: Resize only.
  • freeRotate: Combines the rotate and scale modes into one.
  • lock: Locks the image in place. Disables any user gestures, toolbar actions, or hotkeys that are not associated with mode. Exception: L.ExportAction will still be enabled.

In the below example, the image will be initialized with "freeRotate" handles:

img = L.distortableImageOverlay('example.jpg', {
  mode: 'freeRotate',
}).addTo(map);

If you select a <code>mode</code> that is removed or unavailable, your image will just be assigned the first available <code>mode</code> on initialization.

<hr>

Limiting modes:

<hr> <blockquote>Each <code>mode</code> is just a special type of action, so to ensure that these are always in sync the <code>modes</code> available on an image instance can be limited by the <code>actions</code> available on it. <strong>To remove a mode, limit its corresponding action via the <code><a name="Actions">actions</a></code> option during initialization.</strong> This holds true even when <code>suppressToolbar: true</code> is passed.</blockquote>

In the below example, the image will be initialiazed with 'freeRotate' handles, and limit its available modes to 'freeRotate' and 'scale'.

  • We also remember to add the normal toolbar actions we will want:
img = L.distortableImageOverlay('example.jpg', {
  mode: 'freeRotate',
  actions: [L.FreeRotateAction, L.ScaleAction, L.BorderAction, L.OpacityAction],
}).addTo(map);

Likewise, it is possible to remove or add actions during runtime (addTool, removeTool), and if those actions are modes it will remove / add the mode.

Rotation

rotation (optional, default: {deg: 0, rad:

View on GitHub
GitHub Stars285
CategoryDevelopment
Updated1mo ago
Forks289

Languages

JavaScript

Security Score

95/100

Audited on Feb 2, 2026

No findings