Giter Site home page Giter Site logo

publiclab / leaflet.distortableimage Goto Github PK

View Code? Open in Web Editor NEW
268.0 16.0 284.0 56.25 MB

A Leaflet extension to distort or "rubber sheet" images

Home Page: https://publiclab.github.io/Leaflet.DistortableImage/examples/

License: BSD 2-Clause "Simplified" License

JavaScript 85.22% HTML 14.78%

leaflet.distortableimage's Introduction

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 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.

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

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:


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

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);
Note: You must instantiate a blank collection, then dynamically add layers to it like above. This is because DistortableCollection internally uses the layeradd event to enable additional editing features on images as they are added, and it is only triggered when they are added dynamically.

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:

addGoogleMutant(opts? <Mutant options>): this
  • Adds a Google Mutant layer with location labels according to our recommended setup.
  • Mutant options: {[mutantOpacity][, maxZoom][, minZoom][, labels][, labelOpacity][, doubleClickLabels]}
    • mutantOpacity (default 0.8, value: number 0..1)
      • Same as Leaflet's L.TileLayer opacity option.
    • maxZoom (default: 18, value: number 0..21)
      • Same as Leaflet's L.TileLayer maxZoom option, except has a maximum value of 21 because higher zoom levels on the mutant layer will result in an error being thrown.
      • The mutant layer will appear blurry for zoom levels exceeding 18.
    • minZoom (default: 0, value: number 0..maxZoom)
      • Same as Leaflet's L.TileLayer minZoom option.
    • labels (default: true, value: boolean)
      • If set to false, the mutant layer will not have location labels.
    • labelOpacity (default: 1, value: number 0, 1)
      • If set to 0, labels will be initially invisible.
      • Set to undefined if labels: false is also passed.
    • doubleClickLabels (default: true, value: boolean)
      • Label visibility (opacity) is toggled between 0 and 1 on map dblclick. To turn this functionality off, set this option to false.
      • Set to undefined if labels: false is also passed.
  • Mutant options are saved on the map and accessible during runtime as map.mutantOptions.

And the following custom handlers:

doubleClickLabels: this
  • Allows toggling label visibility on map dblclick.
  • Enabled by default on #addGoogleMutant unless the options labels: false or doubleClickLabels: false are passed to it.
    • If labels: false passed, removed from map altogether.
    • If there are labels present but doubleClickLabels: false was passed, just disabled and can always be enabled during runtime via Leaflet's Handler API.
  • Disables the map's default doubleClickZoom handler when enabled.
boxCollector: this
  • Overrides the map's default boxZoom handler. To use boxZoom instead, pass the options { boxCollector: false, boxZoom: true } to the map on initialization.
  • Allows multiple images to be collected when shift + draging on the map for the multiple image interface.

We have slightly changed a default Leaflet handler:

doubleClickZoom: this
  • This handler may not be enabled (and will return false) while the doubleClickLabels handler is enabled.
  • This handler and doubleClickLabels time and fire a custom singleclick event on map click.

Our "doubleClick" handlers mentioned above use a custom singleclick event to run logic on map dblclick while allowing the images on the map to remain selected. You can read more about the implications of this and how to disable it on our wiki "singleclick event".

L.DistortableImageOverlay


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

getCorner(idx <number 0..3>): LatLng
  • Returns the coordinates of the image corner at index.
getCorners(): 4 [LatLng, LatLng, LatLng, LatLng]
  • Returns the coordinates of the image corners in NW, NE, SW, SE order.
setCorner(idx <number 0..3>, LatLng): this
  • Updates the coordinates of the image corner at index to LatLng and, where applicable, marker and toolbar positioning.
  • We use this internally for distort mode.
setCorners(LatLngCorners): this
  • Same as #setCorner, but takes in a "corners" object made up of LatLngs to update all 4 corners with only one UI update at the end.
  • We use this internally for image translation, rotation, and scaling.
  • LatLngCorners: { keys: <number 0..4>, values: LatLng }
    ex.
    var scaledCorners = {};
    var i;
    var p;
    
    for (i = 0; i < 4; i++) {
      p = map
        .project(img.getCorner(i))
        .subtract(center)
        .multiplyBy(scale)
        .add(center);
      scaledCorners[i] = map.unproject(p);
    }
    
    img.setCorners(scaledCorners);
    
setCornersFromPoints(PointCorners): this
  • Same as #setCorners, but takes in a "corners" object made up of Points instead of LatLngs.
  • PointCorners: { keys: <number 0..4>, values: Point }
getCenter(): LatLng
  • Returns the center (centroid) of the image.
getAngle([unit = 'deg'] <string>): Number
  • Returns the image's rotation angle in units, or in degrees by default.
  • Number will always be >= 0.
  • unit (optional, default: 'deg', value: string 'deg'|'rad')
    ex.
    img.getAngle();
    img.getAngle('deg');
    img.getAngle('rad');
    
setAngle(angle <number>, [unit = 'deg'] <string>): this
  • Sets the image's rotation to angle in units, or in degrees by default.
  • unit (optional, default: 'deg', value: string 'deg'|'rad')
    ex.
    img.setAngle(180);
    img.setAngle(180, 'deg');
    img.setAngle(Math.PI, 'rad');
    
rotateBy(angle <number>, [unit = 'deg'] <string>): this
  • Rotates the image relative to its current angle by angle in units, or in degrees by default.
  • unit (optional, default: 'deg', value: string 'deg'|'rad')
    ex.
    img.rotateBy(180);
    img.rotateBy(180, 'deg');
    img.rotateBy(Math.PI, 'rad');
    
scaleBy(factor <number>): this
  • Scales the image by the given factor and calls #setCorners.
  • A scale of 0 or 1 will leave the image unchanged - but 0 causes the function to automatically return.
  • A negative scale will invert the image and, depending on the factor, change its size.
  • Ex. img.scaleBy(0.5)
restore(): this
  • Restores the image to its natural dimensions, scale, rotation, and location on the map.
isSelected(): Boolean
  • Returns true if the individual image instance is selected.
select(): this
  • Selects an individual image instance.
  • If its editing handler is disabled or the multiple image interface is on (imgGroup.anyCollected() === true), does not select and instead just returns undefined.
  • Internally invoked on image click.
deselect(): this
  • Deselects an individual image instance.
  • If its editing handler is disabled, does not deselect and instead just returns undefined.
  • Internally invoked on map click and image collect (shift + click).

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.

Note: The main difference between the enable / disable runtime API and using the editable option during initialization is in runtime, neither individual image instaces nor the collection group get precedence over the other.
enable(): this
  • Sets up the editing interface (makes the image interactive).
  • Called internally by default (editable), but unlike the option it can be used in runtime and is not ignored if there is a collection group. In fact...
  • ...An individual image can be enabled while the group is disabled. i.e. calling img.editing.enable() after imgGroup.editing.disable() is valid. In this case, the single image interface will be available on this image but not the multi-image interface.
disable(): this
  • Deselects the image, and disables its editing interface (makes it non-interactive).
  • Called internally by default on image deletion.
  • An individual image can be disabled while the group is enabled.
enabled(): Boolean
  • Returns true if editing on the individual image instance is enabled.
  • img.editing.enabled()
hasMode(mode <string>): Boolean
  • Returns true if the image has the passed mode.
getMode(): String
  • Returns the current mode of the image.
getModes(): Hash
  • Returns all the modes available on the image.
nextMode(): this
  • Sets the mode of the image to the next one in the modes array by passing it to #setMode.
  • If the image's editing interface is not enabled or modes only has 1 mode, it will instead return undefined and not update the image's mode.
  • We use this internally to iterate through an image's editing modes easily on dblclick, but you can call it programmatically if you find a need. Note that dblclick also selects the image (given it's not disabled and the collection interface is not on).
setMode(mode <string>): this
  • Sets the mode of the image to the passed one given that it is in the modesarray, it is not already the current mode, and the image editing interface is enabled. Otherwise, does not set the mode and instead just returns undefined.

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".

isCollected(img <DistortableImageOverlay>): Boolean
  • Returns true if the passed L.DistortableImageOverlay instance is collected, i.e. its underlying HTMLImageElement has a class containing "selected".
anyCollected(): Boolean
  • Returns true if any L.DistortableImageOverlay instances are collected.

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

recreateImagesFromJsonUrl(string): {avg_cm_per_pixel , imgCollectionProps<{}>}
  • Returns imageCollectionIbject if successful or empty object if unsuccessful

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.

enable(): this
  • Sets up the multi-editing interface.
  • Called internally by default, see editable.
  • Calls each individual image's #enable method and then enables the multi-image interface.
disable(): this
  • Removes the editing interface (makes the image non-interactive, removes markers and toolbar).
  • Called internally by default on image group deletion, but can also be used for custom behavior.
  • Calls each individual image's #disable method and disables the multi-image interface.
enabled(): Boolean
  • Returns true if editing on the collection instance is enabled.
  • imgGroup.editing.enabled()
removeTool(action <EditAction>): this
  • Removes the passed tool from the control toolbar in runtime if the tool is present.
  • ex: imgGroup.removeTool(Deletes)
addTool(action <EditAction>): this
  • Adds the passed tool to the end of the control toolbar in runtime.
replaceTool(old <EditAction>), next <EditAction>)
  • Replaces the first parameter with the second parameter. Returns the parent object.
hasTool(action <EditAction>): Boolean
  • Returns true if the tool is present in the currently rendered control toolbar.

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

leaflet.distortableimage's People

Contributors

7malikk avatar anishshah101 avatar ashism766 avatar atulyajaiswal avatar dependabot-preview[bot] avatar dependabot-support avatar dependabot[bot] avatar elijahndibe avatar emmanuel-lud avatar fonkwe avatar francofrancis avatar jennifer-tech avatar jesutobi avatar justinmanley avatar jywarren avatar khadeeejah avatar leilayesufu avatar liliyao2022 avatar m1racle04 avatar olamideazeez avatar peculiare avatar realrichi3 avatar rexagod avatar sashadev-sky avatar segun-codes avatar sudeep162002 avatar themacboy avatar tildadares avatar vanithaak avatar vladimirmikulic avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

leaflet.distortableimage's Issues

Why are the matrices doctored before and after the projective transformation?

@anishshah101 - Values are loaded into the matrix very carefully before the transformation and then rearranged after the projective transformation - why?

Specifically, on L109:

var t = $L.general2DProjection(0,  0, x1, y1, 
                               w,  0, x2, y2, 
                               0,  h, x3, y3, 
                               w,  h, x4, y4
);

and then, a few lines down:

for(i = 0; i != 9; ++i) t[i] = t[i]/t[8];
t = [t[0], t[3], 0, t[6],
     t[1], t[4], 0, t[7],
     0   , 0   , 1, 0   ,
     t[2], t[5], 0, t[8]];

I'm sure there's a good reason - I just don't understand it. Is there a paper you found that explains this? Would love it if you could pass on some of the mathematical / technical resources you found over the summer that explain why this works!

odd/wild distortions on zooming in Firefox

sometimes only minor (~30%?) changes in corner positions, briefly during zooming, and sometimes radical distortions as corners get moved off screen during zooms. This does not happen in Safari or Chrome on Mac, but does on Firefox 35 for Mac and Ubuntu and Firefox for Android.

screen shot 2015-02-05 at 12 43 52 pm

Should be (note probably unrelated partial-load issues as reported in publiclab/mapknitter#83):

screen shot 2015-02-05 at 12 43 51 pm

locked markers are still draggable

there's a bug in Leaflet that's been patched but is not yet in their stable release:

http://stackoverflow.com/questions/22671733/why-doesnt-marker-dragging-disable-work
Leaflet/Leaflet@c6dea37

It means I can't disable dragging of markers after we've set a custom marker icon (oddly, but that's the bug).

See our code here: https://github.com/publiclab/Leaflet.DistortableImage/blob/master/src/DistortableImageOverlay.js#L190
What's should our preferred practice be here? depend on an edge release, or monkey patch it, or what?

initialize _corners earlier; don't wait for images to load

perhaps show a grey box instead of an image until it's loaded?

isn't there a way to get image dimensions or at least setup image._corners before image load, even provisionally, so we don't see the undistorted shape, and so that the correctly distorted shape is placed correctly at the very beginning? There must be.

revisit conventions for select/lock/read only

Things to consider:

  • when to show Xs for locked state (currently selected but not editable)
  • when not to show Xs ('read only' mode, to just see the map)
  • interaction/interface to lock/unlock
  • no toolbar for "read only" images
  • locked images should still be transparent-able and outline-able

All states; selected/unselected, locked/unlocked, read only/editable

Refactor image border drawing (css border is affected by image transparency)

so we can't use opacity to make images outlined; we may have to draw a new polygon in the way Leaflet implements Polygon?

or we could ditch outlining and create a new tool like "isolate" which would hide all other images besides the one you're working on. Which may be more helpful than outline in the first place.

and hide, i guess

Also: a solution here may also solve the issue that extreme distortions reveal that a 1px border is relative to the local image distortions; that is, a highly stretched corner of an image will have a blurry, wider red border.

locking "X" icons for images

The Xs are for a selected but not unlocked image; seems like this may have been lost or removed in recent commits, in which the only way to unlock seems to be the L hotkey.

Circles not tracking with image

The circles that are supposed to be on the coners of my picture are not tracking with my image. I'm using Firefox with a Linux OS

screenshot

selection of an image does not deselect other images

@manleyjster's encouraged no global listing of images in this plugin, which is reasonable; but how shall we address the user's expectation that clicking on one image deselects the rest?

This idea seems brittle: each image creates a map-level click observer that deselects self unless that click propagates down to self.

Addendum; multiple images selected at the same time makes hotkey control somewhat difficult, although we could have each image register itself onSelect in application code to track the most recently interacted-with image, to direct hotkey commands to. But without a visual indication of "last-selected" this seems labored as well. I think perhaps only-one-selected-image is a good model.

must click images first to select before dragging

Preferred behavior is that you can just grab and drag images without 2 clicks.

This happens because the Draggable instantiation and initialization occurs on the same event as the selecting/enabling, and presumably after. Maybe some combination of firing events like .fire('mousedown') could help, but I don't understand how Leaflet's Draggable really works...

get Toolbar to continue to appear on Firefox Android and Chrome Android after deselecting and re-selecting an image

Toolbars are not showing after the first image on Firefox Android, and Chrome Android. To reproduce:

  1. open example in FF or Chrome, both on Android
  2. click on an unlocked image, see Toolbar appear
  3. click on the image again in a different spot, see Toolbar disappear and reappear in new mouse location (correct behavior)
  4. click on another image
  5. click on the original image again, see Toolbar appear
  6. click on the image again in a different spot, but Toolbar does not move or reappear

Step 6 can also be done by moving the image and trying to get the Toolbar to reappear. This bug can be circumvented by clicking another image and then clicking the first image again; Toolbar appears on first click, but not on subsequent ones.

Leaflet events reference: http://leafletjs.com/reference.html#events

Key observations:

  • this only affects unlocked images. But I've found it hard to monitor attached events on a mobile device, and the bug does not appear on a desktop browser
  • this occurs for touch or mouse click events; I plugged a mouse into my Android phone and found the same behavior
  • no other events (dblclick, for example) attached appear to be triggered anymore after the first time the Toolbar appears (i.e. after step 5 above)
  • originally, the Toolbar did not appear at all after step 4 above, but changing this line to use L.DomEvent.on() instead of overlay.on() got the initial selection click to trigger Toolbar appearance.

Mapknitter in Safari: stitch separates from base layer when dragging

Safari 8.0.8
when I drag the base layer it separates from the stitched map, both stay in the same zoom level when zoom in and out. When I double click an image for the edit menu it zooms in.
Detailed description: I refresh the page and start by dragging then zoom out, double click an image on the right side of the map and a different image is clicked, the one that is to the left. I click the last one and it zooms in. I click a different one, now it works, I click it again and it doesn't respond. I click the image to the left, and the one to the right is on with the menu. I click it and the one to the left is on. It seems as though clicking has shifted to the left from the position of the mouse. Now everywhere I click on the map it turns on images to the right.
If I click on images on the left side of the map it zooms in.
The menu itself doesn't respond. instead it jumps to a different image.
when I try to use the twist/scale handles sometimes it works, sometimes it drags the base layer.
some images appear transparent.
I will file some of these as different issues.

choose/refine interaction to display image popup toolbar

We need to choose the interaction which causes it to pop up and where -- i think it pops up on any mouseUp event, at the mouse location? What if we had it pop up

a) at the image centroid (or averaged center) instead,

and/or

b) only when the image is clicked once but not dragged?

relative iconUrl breaks depending on application setup

iconUrl: '../src/images/close_444444_16.png',

In previous versions we had a kludgy 'imagesPath' (or something) as an option in the global $L initializer. My thought is to do that for Leaflet.DistortableImage. @manleyjster, does that sound reasonable? We'd presumably have to have it be an distortableImage.options and then also referenced in the L.EditHandle constructors.

display Leaflet.Toolbar on right click (contextmenu)

This as a possible workaround related to #30 (and a reasonable expectation of user in any case) - use the Leaflet event 'contextmenu' to display Leaflet.Toolbar on right click (or long press in mobile).

Just opening for debate :-)

mapknitter in firefox slow save image

Takes long time to show changes after refresh. If I open the map in Safari it shows the changes, while in firefox it can refresh a few time with the older version.

multiple image collection actions (like export)

Part 1: -- this part is done.
This is becoming higher priority now, and would at most basic, include the ability to select and display as selected, multiple images.

Part 2: -- -- this part is done.
We should be able to return an array of selected images, and to perform actions using their collected image URLs or other properties.

Part 3: -- this part is done.
Eventually we should consider a menu for the manipulation of image collections, for example initiating an export. This relates to publiclab/mapknitter#326, the new MapKnitter Exporter UI, but could also be used for simple things like dragging a group of images together.

Part 4: -- Open to claim
Get all actions available in the single image interface working in the collection interface:

  • Export
  • Delete
  • ToggleTransparency
  • Restore
  • ToggleOutline
  • ToggleRotateScale
  • ToggleScale
  • ToggleRotate
  • EnableEXIF
  • ToggleLock

img.readOnly option

very useful for just displaying/sharing. Prob. can base on separation of interaction w/ distortion code from @manleyjster

use rotate as the default starting behavior

Is there any way to use the rotate mode as the default behavior?
I tried to set the mode to rotate on initialization but had no luck. I also tried to set the mode manually but had no luck either.

im=new L.DistortableImageOverlay(im_url,mode:'rotate').addTo(map);
im.editing._mode='rotate'

_corners is nil during animateZoom just after image load

Uncaught TypeError: Cannot read property '0' of undefined
L.DistortableImageOverlay.L.ImageOverlay.extend._calculateProjectiveTransform
L.DistortableImageOverlay.L.ImageOverlay.extend._animateZoom

this occurs in MapKnitter 2; will try to provide more detail, but I believe it's based on a map.fitBounds() call just after images load. This does not stop anything from loading correctly, but it may account for some of the weird distortion of images during zoom.

See if you can see this at http://test.mapknitter.org:3000/map/2013-05-24-california-oakland-forestland-manzanita-

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.