npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

leaflet-distortableimage-updated

v1.0.4

Published

Leaflet plugin enabling image overlays to be distorted, stretched, and warped (built for Public Lab's MapKnitter: http://publiclab.org).

Downloads

378

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

Check out an early prototype of the Mapknitter Lite project

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 shift + click on an image or shift + 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);
});

Note: map.addGoogleMutant() 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.

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 mode that is removed or unavailable, your image will just be assigned the first available mode on initialization.

Limiting modes:

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: 0}, value: hash)

Set the initial rotation angle of your image, in degrees or radians. Set the unit as the key, and the angle as the value.

img = L.distortableImageOverlay('example.jpg', {
  rotation: {
    deg: 180,
  },
}).addTo(map);

Selected

selected (optional, default: false, value: boolean)

By default, your image will initially appear on the screen as unselected (no toolbar or markers). Interacting with it will make them visible.

If you prefer that an image initially appears as selected instead, pass selected: true.

Note: when working with the multi-image interface, only the last overlay you pass selected: true to will appear with editing handles and a toolbar.

Suppress Toolbar

suppressToolbar (optional, default: false, value: boolean)

To initialize an image without its L.Popup instance toolbar, pass it suppressToolbar: true.

Typically, editing actions are triggered through our toolbar interface. If disabling the toolbar, the developer will need to implement their own toolbar UI connected to our actions (WIP API for doing this)

Multiple Image Interface

Our DistortableCollection class builds on the single image interface to allow working with multiple images simultaneously.

The setup is relatively similar.

Although not required, you will probably want to pass corners to individual images when adding multiple or they will be positioned on top of eachother.

Here is an example with two images:

// 1. Instantiate map
// 2. Instantiate images but this time *dont* add them directly to the map
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),
  ],
});

img2 = L.distortableImageOverlay('example.jpg', {
  corners: [
    L.latLng(51.51, -0.20),
    L.latLng(51.51,-0.16),
    L.latLng(51.49, -0.21),
    L.latLng(51.49,-0.17),
  ],
});

// 3. Instantiate an empty `DistortableCollection` group
imgGroup = L.distortableCollection().addTo(map);

// 4. Add the images to the group
imgGroup.addLayer(img);
imgGroup.addLayer(img2);

Options available to pass during L.DistortableCollection initialization:

Actions

  • actions (optional, default: [L.ExportAction, L.DeleteAction, L.LockAction, L.UnlockAction], value: array)

Overrwrite the default toolbar actions for an image collection's L.Control toolbar. Reference the available values here.

For example, to overrwrite the toolbar to only include the L.DeleteAction:

imgGroup = L.distortableCollection({
  actions: [L.DeleteAction],
}).addTo(map);

To add / remove a tool from the toolbar at runtime, we have also added the methods addTool(action) and removeTool(action).

Editable

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

See editable.

Suppress Toolbar

suppressToolbar (optional, default: false, value: boolean)

Same usage as suppressToolbar, but for the collection group's L.Control toolbar instance.

This provides the developer with the flexibility to keep the popup toolbars, the control toolbar, both, or neither.

For ex.

// suppress this images personal toolbar
img = L.distortableImageOverlay('example.jpg', {
  suppressToolbar: true,
  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),
  ],
});

// suppress the other images personal toolbar
img2 = L.distortableImageOverlay('example.jpg', {
  suppressToolbar: true,
});

// suppress collection toolbar accessed during multi-image selection
imgGroup = L.distortableCollection({
  suppressToolbar: true,
}).addTo(map);

Tooltip Text

tooltipText (optional, default: '', value: string) This provides the flexibility to add tooltip text to every image placed on the tile layer.

For ex.

// Sets up tooltip text for an image, the text is displayed when mouse is placed on it
img = L.distortableImageOverlay(
       'example.jpg', 
       {tooltipText: 'Sample text'}
);

UI and functionalities

Currently it supports multiple image selection and translations, and WIP we are working on porting all editing tools to work for it, such as opacity, etc. Image distortions (via modes) still use the single-image interface.

A single toolbar instance (using L.control) renders the set of tools available to use on collections of images.

collect:

  1. Collect an indvidiual image with shift + click.
  2. Or for touch devices, touch + hold (aka longpress).
  3. Collect multiple images at once with shift + drag (Uses our L.Map.BoxCollector).

decollect:

  • In order to return to the single-image interface, where each L.popup toolbar only applies actions on the image it's attached to, you must toggle all images out of collection with shift + click / touch + hold, or...
  • ...Click on the map or hit the esc key to quickly decollect all.

Toolbar Actions (& Keybindings)


Single Image Interface


Default tools

  • L.BorderAction (b)
    • Toggles a thin border around the overlay.
  • L.DeleteAction (backscpace, delete)
    • Permanently deletes the image from the map. Uses a confirm() modal dialog.
    • windows backspace / mac delete
  • L.DistortAction (d)
    • Sets distort mode.
  • L.DragAction
    • Sets drag mode.
  • L.ExportAction (e)
  • L.FreeRotateAction (f)
    • Sets freeRotate mode.
  • L.LockAction (l, u)
    • Toggles between lock mode and the initially set default mode (distort by default).
  • L.OpacityAction (o)
  • L.RotateAction (r):
    • Sets rotate mode.
  • L.ScaleAction (s):
    • Sets scale mode.

Add-on tools

These may be added using addTool(), like this:

distortableImageLayer.editing.addTool(L.StackAction);
  • L.RestoreAction
    • Restores the image to its natural dimensions, scale, rotation, and location on the map.
  • L.StackAction (q, a)
    • Switch an image's overlap compared to neighboring images back and forth into view. Employs bringToFront() and bringToBack() from the Leaflet API.
  • L.GeolocateAction (WIP)

Multiple Image Interface


Defaults:

  • L.ExportAction (e)
  • L.DeleteAction (backscpace, delete)
    • Permanently deletes a collection of images from the map.
  • L.LockAction (l)
    • Sets lock mode for a collection of images.
  • L.UnlockAction (u)
    • Unsets lock mode for a collection of images.

Quick API Reference


L.Map


We have extended Leaflet's L.Map to include a convenience method for this library:


L.DistortableImageOverlay


An individual image instance that can have transformation methods called on it and can be "selected".


L.DistortableImageOverlay.Edit


A handler that holds the keybindings and toolbar API for an image instance. It is always initialized with an instance of L.DistortableImageOverlay. Besides code organization, it provides the ability to enable and disable image editing using the Leaflet API.


L.DistortableCollection


A collection instance made up of a group of images. Images can be "collected" in this interface and a "collected" image is never also "selected".

Retrieve image from Json file containing image property set. The property set can be used to instantiate new imageOverlays.

Example // 1. Instantiate an empty distortableCollection imgGroup = L.distortableCollection().addTo(map);

// 2. Get property set for each of the images const imageCollectionObj = await map.imgGroup.recreateImagesFromJsonUrl(jsonDownloadURL);

Note: jsonDownloadUrl must be in either of these formats: i. https://archive.org/download/mkl-2-2/mkl-2-2.json (for json files generated from Mapknitter-Lite) - "mkl-2-2" is the identifier provided by Internet Archive after a file is uploaded to the service (i.e., archive.org) - "mkl-2-2.json" name of the Json file
ii. https://archive.org/download/mapknitter/--10.json (for json files from legacy mapknitter.org) - "mapknitter" is the path for all the legacy Json files and must be present in the URL - "--10.json" is th name of the Json file

// 3. Iterate through each of the property sets, extract the imageURL, tooltipText and corners imageCollectionObj then place each of them on the tile map using:
image = L.distortableImageOverlay(imageURL,{tooltipText, corners}); map.imgGroup.addLayer(image);


L.DistortableCollection.Edit


Same as L.DistortableImage.Edit but for the collection (L.DistortableCollection) instance.

Additional Components

Keymapper

// add a position option with combinations of 'top', 'bottom', 'left' or 'right'
L.distortableImage.keymapper(map, {
  position: 'topleft',
});

Options:

  • position (optional, default: 'topright', value: string)

Adds a control onto the map which opens a keymapper legend showing the available key bindings for different editing / interaction options.

(WIP) Currently includes keybindings for all available actions and does not update yet if you use the actions API to limit available actions.

Custom Translations

You can translate the LDI toolbar buttons in your native language by providing a custom translation object to DistortableImageOverlay or DistortableCollection.

NOTE: If you don't specify a custom translation for a certain field, it will fallback to English.

These are the defaults:

var translation = {
  deleteImage: 'Delete Image',
  deleteImages: 'Delete Images',
  distortImage: 'Distort Image',
  dragImage: 'Drag Image',
  exportImage: 'Export Image',
  exportImages: 'Export Images',
  removeBorder: 'Remove Border',
  addBorder: 'Add Border',
  freeRotateImage: 'Free rotate Image',
  geolocateImage: 'Geolocate Image',
  lockMode: 'Lock Mode',
  lockImages: 'Lock Images',
  makeImageOpaque: 'Make Image Opaque',
  makeImageTransparent: 'Make Image Transparent',
  restoreImage: 'Restore Natural Image',
  rotateImage: 'Rotate Image',
  scaleImage: 'Scale Image',
  stackToFront: 'Stack to Front',
  stackToBack: 'Stack to Back',
  unlockImages: 'Unlock Images',
  confirmImageDelete: 'Are you sure? This image will be permanently deleted from the map.',
  confirmImagesDeletes: 'Are you sure? These images will be permanently deleted from the map.',
};

For confirmImagesDeletes you can pass a function that returns a string. This is useful for languages where noun form depends on the number:

var translation = {
  confirmImagesDeletes: function(n) {
    var cond = n%10 >= 2 && n%10 <= 4 && n%100 - n%10 !== 10;
    var str = 'Czy na pewno chcesz usunąć ' + n;
    if(cond) str += ' obrazy?';
    else str += ' obrazów?';
    return str;
  },
  // ...
}

L.DistortableImageOverlay

img = L.distortableImageOverlay('example.jpg', {
  translation: {
    deleteImage: 'Obriši sliku',
    distortImage: 'Izobliči sliku',
    dragImage: 'Pomjeri sliku',
    // ...
  },
}).addTo(map);

L.DistortableCollection

imgGroup = L.distortableCollection({
  translation: {
    deleteImages: 'Obriši slike',
    exportImages: 'Izvezi slike',
    // ...
  },
}).addTo(map);

Contributing

See CONTRIBUTING.md for details on how you can contribute to Leaflet.DistortableImage.

Contributors

Many more at https://github.com/publiclab/Leaflet.DistortableImage/graphs/contributors