GithubHelp home page GithubHelp logo

wildhoney / leaflet.freedraw Goto Github PK

View Code? Open in Web Editor NEW
539.0 29.0 102.0 17.33 MB

:earth_asia: FreeDraw allows the free-hand drawing of shapes on your Leaflet.js map layer – providing an intuitive and familiar UX for creating geospatial boundaries similar to Zoopla and others. Included out-of-the-box is the concaving of polygons, polygon merging and simplifying, as well as the ability to add edges and modify existing shapes.

Home Page: https://freedraw.herokuapp.com/

License: MIT License

JavaScript 89.03% CSS 5.14% HTML 5.83%
maps polygon markers shape zoopla geospatial draw

leaflet.freedraw's Introduction

FreeDraw allows the free-hand drawing of shapes on your Leaflet.js map layer – providing an intuitive and familiar UX for creating geospatial boundaries similar to Zoopla and others. Included out-of-the-box is the concaving of polygons, polygon merging and simplifying, as well as the ability to add edges and modify existing shapes. Note: For drawing polylines instead, try L.Pather.

Travis   npm   Bower   MIT License

Table of Contents

  1. Browser Support
  2. Getting Started
  3. Markers
  4. Modes
  5. Options
  6. Classes
  7. Methods
  8. Predefined Polygons

FreeDraw Screenshot

Browser Support

IE10 onwards

Getting Started

FreeDraw functions as a standard Leaflet module, meaning you initialise it and add it to your map layer via the addLayer function on your map instance – when you instantiate FreeDraw you can pass a set of options for behaviour customisation.

import L from 'leaflet';
import FreeDraw from 'leaflet-freedraw';

const map = new L.Map(node);
const freeDraw = new FreeDraw();

map.addLayer(freeDraw);

By attaching FreeDraw to your map layer, an SVG node will be appended to the DOM, and mouse event listeners will be attached to the map instance for creating and managing the geospatial polygons.

Adding Markers

When a user creates a polygon an event is fired on the map instance called markers which you can listen for by using the native Leaflet on function.

freeDraw.on('markers', event => {
    console.log(event.latLngs);
});

Note: You can obtain the event type through the event.eventType field – such as create, edit, etc...

Once you have received the latitude and longitude values the next step would likely be to perform any necessary geospatial queries, and then render the relevant markers onto the map – for this you could use L.Marker and the native addTo method for placing markers on the map – however the important take-away is that FreeDraw doesn't concern itself with marker placement, as this is sufficiently covered by Leaflet.

Setting Modes

By default the mode is ALL which means all actions can be performed on the FreeDraw layer — create, edit, delete, and append — you're able to modify the mode at any time by using the mode method, or upon instantiation by passing an object as the first argument.

import L from 'leaflet';
import FreeDraw, { CREATE, EDIT } from 'leaflet-freedraw';

const map = new L.Map(node);
const freeDraw = new FreeDraw({
    mode: CREATE | EDIT
});

By passing in the mode as CREATE | EDIT you're only allowing the user to create and edit polygons, they are not able to append edges, nor delete them. You may use the mode method post-instantiation to modify the mode at any time – in the case below to also allow deleting of polygons.

// Allow create, edit and delete.
freeDraw.mode(CREATE | EDIT | DELETE);

// Allow everything except create.
freeDraw.mode(ALL ^ CREATE);

// Allow nothing.
freeDraw.mode(NONE);

Note: Invoking mode without passing a mode simply returns the current mode.

Passing Options

All of the following options can be passed in when instantiating FreeDraw in the same way that we pass mode in the previous examples.

Option Default Result
mode ALL Modifies the default mode.
smoothFactor 0.3 By how much to smooth the polygons.
elbowDistance 10 Factor to determine when to delete or when to append an edge.
simplifyFactor 1.1 By how much to simplify the polygon.
mergePolygons true Whether to attempt merging of polygons that intersect.
concavePolygon true Whether to apply the concaving algorithm to the polygons.
maximumPolygons Infinity Maximum number of polygons to be added to the map layer.
notifyAfterEditExit false Whether to defer markers event until after exiting EDIT mode.
leaveModeAfterCreate false Whether to exit CREATE mode after each polygon creation.
strokeWidth 2 Size of the stroke when drawing.

By using the options above we can tweak how FreeDraw functions – whilst some of the options have obvious effects, others are much more tweak and see based on your expected outcome – such as the subjective simplifyFactor and elbowDistance options.

Classes

Depending on the current modes active on the map instance, the relevant classes are applied to the map container that you instantiated L.Map with – by using these class names it allows you to tailor the UX to the current mode, such as changing the cursor to crosshair when the user is allowed to create polygons.

Class Name Mode
mode-none NONE
mode-create CREATE
mode-edit EDIT
mode-delete DELETE
mode-append APPEND
.map.mode-create {
    cursor: crosshair;
}

From the above example if the current mode is CREATE | EDIT | APPEND then the three class names that will be present on the map node will be mode-create, mode-edit and mode-append, allowing you to provide a better UX from within your attached stylesheet.

Bundled Methods

With the instance of freeDraw there are certain methods for manipulating FreeDraw directly, such as creating polygon from a set of latitude and longitude values.

Method Yields Result
create Array Creates a polygon by passing an array of LatLngs
remove void Removes a polygon that is yielded from create
clear void Clears all polygons from the current instance
mode Number Sets and retrieves the current mode.
cancel void Cancels the current create action – such as on escape.
size Number Yields the number of polygons on the map layer.
all Array Enumerate all of the current polygons for the current layer

When using the create method to create polygons from an array of latitude and longitude values, the CREATE mode is disregarded, which means it doesn't need to be enabled to create to succeed – if you would like such behaviour then you could simply assert that CREATE is enabled.

import L, { LatLng } from 'leaflet';
import FreeDraw from 'leaflet-freedraw';

const map = new L.Map(node);
const freeDraw = new FreeDraw();

// Create a polygon based on the given lat/long values.
const polygons = freeDraw.create([
    new LatLng(51.50046151184328, -0.08771896362304689),
    new LatLng(51.50067523261736, -0.09175300598144533),
    new LatLng(51.50329323076107, -0.09106636047363283),
    new LatLng(51.50409462869737, -0.08763313293457033)
]);

// Remove the created polygons from the map.
polygons.forEach(polygon => freeDraw.remove(polygon));

// Alternatively you could have cleared ALL polygons.
freeDraw.clear();

Note: create method returns an array of polygons, as often it may yield more than one.

In the case of the cancel method it's often desirable to cancel the creation of the polygon when the escape key is pressed – for this you simply need to attach an event to the document.body.

document.addEventListener('keydown', event => {

    // Cancel the current action when the escape key is pressed.
    event.key === 'Escape' && freeDraw.cancel();

});

Predefined Polygons

You can add polygons to the map layer at any point by using the create method. You must remember to setup FreeDraw through Leaflet, rather than instantiating FreeDraw without tying it to a Leaflet map instance.

freeDraw.create([
    new L.LatLng(51.50046151184328, -0.08771896362304689),
    new L.LatLng(51.50067523261736, -0.09175300598144533),
    new L.LatLng(51.50329323076107, -0.09106636047363283),
    new L.LatLng(51.50409462869737, -0.08763313293457033)
]);

leaflet.freedraw's People

Contributors

alexcode avatar ankeetmaini avatar dependabot[bot] avatar ernoaapa avatar hotmeteor avatar stev-0 avatar tarikozket avatar uniserpl avatar ventralnet avatar vitalif avatar wildhoney avatar yago 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

leaflet.freedraw's Issues

Provide the ability to run hull algorithm asynchronously

If you draw something that takes a long time (1-3 seconds) to run the hull algorithm, it appears the browser is hung.

An event firing before and after processing might help, but I think would be insufficient -- I tried just adding these and there is not enough time for the DOM to update and put up a loading mask before hanging on the hull algorithm.

We were able to hack around the issue with the following code:

(forgive the coffeescript)

        # Adding the processing class doesn't give enough time
        # to re-draw the DOM before the hull algorithm locks up the browser...
        # To get IE 11 to re-render the DOM, it appears the timeout for the class change -> createMouseUp needs to be
        # at least 20 ms.
        # The RECOUNT_TIMEOUT needs to be bigger than this delay to make the 'markers' event fire after our
        # delayed createMouseUp
        L.FreeDraw.RECOUNT_TIMEOUT = 30
        @freeDrawer._createMouseUp = =>
            @$outerMapContainer.addClass('processing') if @freeDrawer.creating
            setTimeout (=>
                freeDrawerCreateMouseUp.apply(@freeDrawer, arguments)
                @$outerMapContainer.removeClass('processing')
            ), L.FreeDraw.RECOUNT_TIMEOUT - 5

It would be nice if FreeDraw could do one of the following:
a) provide events and run the hull algorithm on a timeout to allow the DOM to be masked
b) (even better) run the hull algorithm on a web worker to allow the DOM to be responsive while processing.

Note: I've found the most effective shape to make the hull algorithm run slow is a flower shape.

Problem with wrong filename of leaflet.freedraw.min.js

Hi there,
I encountered an error when using leaflet and leaflet.freedraw via bower. After installing the bower component of Leaflet.freedraw, I got an error that leaflet.freedraw.min.js could not be found. Which is perfectly correct, since the file is named leaflet.freedraw.js (missing the '.MIN.').
When I rename the file inside of the bower_components folder and add the '.min.' everything works as expected.

Am I doing something wrong here, or could you fix that little typo?

Cheers,
christian

Add callback function to handle the created and edited polygon

'markers' event only return all markers, but i wish to get the created polygon after drew it.
Now i just modify the _createMouseUp() to add the callback function, can you support it officially?

The edit mode is more difficult when multiple polygon in the map, we need to tell the callback function which polygon had been edited.

Polygon is reset when using leaflet developmen version

I have a project that requires working on Leaflet development version (0.8 - 1.0), I already implemented some features that uses the development version of the code. I wanted to use freedraw in the project. The code is not compatible with the new version, I have tried both the 0.8 and 1.0 and both does not work. It works find with the 0.73 leaflet but I need it to work with the new version.

The problem is the with the new development version the any polygon the user draw are reset immediately and the event.latLngs is always empty array. The code clearly add the polygon then it disappears again and the final is empty event data.
I have tried to debug the code for a little is seems that the polygon is added to the layer in createPolyon but the following code result will clear all the polgyon as it does not reterive any layer with the freedraw polygons. The first thing I noticed is the GROUP_TAG in the development version will have to be "SVG" I changed that but still the layer is not detected and the polygon is not added to the polygons array.

The following code getPolygon
'''

            var GROUP_TAG = 'G';
            for (var layerIndex in this.map._layers) {
                if (this.map._layers.hasOwnProperty(layerIndex)) {
                    var polygon = this.map._layers[layerIndex]; 
                    // Ensure we're dealing with a <g> node that was created by FreeDraw (...an SVG group element).
                    if (polygon._container && polygon._container.tagName.toUpperCase() === GROUP_TAG) {
                        if (polygon instanceof L.FreeDraw.Polygon) {
                            polygons.push(polygon);
                        }
                    }

                }

            }

        } 

'''

Could any one just help on how to solve this. ? what to fix to make it work.

Drawing a polygon in IE11 does not work.

This seems not to be the case in all IE11 minor versions but at least in version 11.0.9600.17351 (update version is 11.0.13 KB2987107). Does not work in my project and does not work on the example app on freedraw.herokuapp.com. I have tried on different computers and get the same result. The problem is in _attachMouseDown, where it tries to bind mousedown and touchstart, this.map.on('mousedown touchstart', function onMouseDown(event).

The callback never gets triggered even though onMouseMove does. Even though I replace mousedown and touchstart with click, the callback never gets triggered. If I change IE emulator to use IE9 it works properly.

Non FreeDraw created polygons are effected by FreeDraw calls

It looks like currently the code will perform its operations on all polygons from all layers on the map. For example if a user has a geoJson layer that paints some polygons a call to "clearPolygons" will remove the polygons painted on the geoJson layer as well as the FreeDraw polygons.

I have a potential fix for this on a branch.
https://github.com/ventralnet/Leaflet.FreeDraw/tree/private/ventralnet/markerPolygon

What do you think about that. Basically involves

  1. Defining a 'marker' interface polygon
     L.FreeDraw.Polygon = L.Polygon.extend({
         options: {
             className:"leaflet-freedraw-polygon"
         }
     });
  1. I included a special className to assign to FreeDraw created polygons that will allow for styling separate from other polygons that may be painted on the screen.

See any issues with this approach? Or should I send the pull request over.

Associated lat/longs change as you zoom

We need consistent lat/longs that are saved when the user creates and edits, but that don't change as the user zooms when the polygon is optimised for the current zoom level.

possibility to predefine a polygon

Hi there,

is there a function like createPolygon(latLngs) with that i can predefine a polygon in the FreeDraw layer
which the user later can edit or delete ?

best regards

How to change the line color when drawing the polygon?

Hi all, the css in example had demo how to change the color after create the polygon, but how can i change the color when i drawing the polygon? current color is purple, i want to change it to blue.

BTW. Freedraw is the best draw plugin :)

Multi layer and Intersection Area in Leaflet

Dear Adam,
Your FreeDraw plugin at "http://freedraw.herokuapp.com/"
is very successful and flexible for drawing poligon. Congratulations for it. Plugin can combine two different poligon if they have intersection area (like union of sets). Is that possible to remove some parts of the poligon by drawing a different poligon with remove module.
Can we define a new layer with different properties. So when I choose a layer, I intent to modify only that layer .
thanks

Add Holes to Polygon

createPolygon method is not accepting polygons with a Region and holes. http://leafletjs.com/reference.html#polygon says "You can also create a polygon with holes by passing an array of arrays of latlngs, with the first latlngs array representing the exterior ring while the remaining represent the holes inside."

Touch does not work

I've been unable to figure out how to get freedraw to work on a touch input device, whether iPad or Chrome with the touch device emulator.

No idea what the underlying problem is.

Draw polylines?

Is it possible to draw polylines instead of polygons? Allowing people to essentially write words and draw pictures on the map?

Ability to enable/disable tool

It would be nice to be able to enable or disable the entire FreeDraw plugin. Using it in tandem with something like Leaflet Draw isn't possible, as it seems FreeDraw takes over all the events on the map for mousedown/mouseup, etc. I could be wrong, however.

CREATE mode disables scroll wheel zooming and never enables it again

I have first noticed this in my app but even in the provided heroku app you can see the problem.

In the example app, as expected, when in the CREATE mode, the "scroll to zoom" is deactivated. There, after drawing a polygon (since freeDraw.options.exitModeAfterCreate is on), CREATE mode becomes disabled, and, with it, the "scroll to zoom" becomes enabled again. So far, so good. The problem is when, in CREATE mode, you change to some other mode "manually" – in this case, for example, by clicking on some other mode's "only" link before drawing something. Then, the zoom never becomes enabled again.

I suspect that only the "automatic mode switch", like when freeDraw.options.exitModeAfterCreate is active, is calling the correct function to set the defaultPreferences back again. I tried to look into the code, but I got kinda lost... If you can't find the time to try to debug this, you could give me some basic directions and I could try to look deeper into it.

clearPolygons method will always notify boundaries even if called with this.silenced===true

It looks like there is a slight bug. When the clearPolygons method is called it first calls this.silently(this._clearPolygons)

The silently method will set this.silenced to true and then execute the callback, then set this.silenced to false. This is erroneous if the clearPolygons method was called with silenced set to true. No matter what after the silently call is made silenced will be false and therefore the notifyBoundaries method will be called.
/**
* @method clearPolygons
* @return {void}
*/
clearPolygons: function clearPolygons() {
this.silently(this._clearPolygons);
if (!this.silenced) {
this.notifyBoundaries();
this.memory.save(this.getPolygons(true));
}
}

/**
* Responsible for polygon mutation without emitting the markers event.
*
* @method silently
* @param callbackFn {Function}
* @return {void}
*/
silently: function silently(callbackFn) {
    this.silenced = true;
    callbackFn.apply(this);
    this.silenced = false;
}

Non-existent polygon

Reproduce:

  • Draw small polygon in fully-zoomed mode;
  • Zoom out and draw another polygon – an error is thrown.

Solution is to apply delete events when zooming to ensure small polygons invisible at a certain zoom level are always deletable.

Programmatically set a polygon

Is there a way to programmatically set a polygon on page load? I've tried the following, to no avail:

var freeDraw = new L.FreeDraw({
    multiplePolygons: false,
    mode: L.FreeDraw.MODES.VIEW
});
var polygon = L.polygon([latLngs]);
freeDraw.addLayer(polygon);

FreeDraw object is automatically reseted

I'm adding a freeDraw layer to a L.layerGroup on my map. Users could draw their polygons and stuff, then they move to another step of my app. By advancing, the L.layerGroup, containing the freeDraw layer, is detached from the map, using the map.removeLayer() (I keep the reference to the variable, however).

The problem is when users want to get back to the previous step. I add, again, the previous L.layerGroup using someLayerGroup.addTo(someMap) function, hoping that the freeDraw layer keeps the same state as before (maintaining the polygons property, for example). However, the whole freeDraw object is reseted.

An example:

var currentLayerGroup;

userChangedStep = function() {
  savePreviousLayer(currentLayerGroup);
  myMap.removeLayer(currentLayerGroup);
  currentLayerGroup = getStepLayer();

  if(freeDrawStep)
    setupDrawing();

  currentLayerGroup.addTo(myMap);
};

setupDrawing = function() {
  if(!currentLayer.freeDraw) { //if freeDraw is not initialised yet
    currentLayerGroup.freeDraw = new L.FreeDraw(myOptions);
    currentLayerGroup.addLayer(currentLayerGroup.freeDraw);
  }
};

Basically, when userChangedStep() is called, and the current step is again the "freeDrawStep", I was hoping to reload every polygon that the user previously drew, since the freeDraw layer is associated with the current layerGroup. However, all the properties on the freeDraw object are reseted.

So... Is this a bug or a feature?

Support conventional polygon click mode

Currently the polygon can be drawn by holding down the mouse button.
Releasing it closes the polygon.

I would prefer if the tool would start in a conventional polygon click mode
which allows to create polygon points (and therefore edges)
by clicking at several points one after another.
As soon as the user holds down the mouse button the FreeDraw mode gets activated.
If he releases the mouse button he can continue in the basic polygon click mode.
Doubleclick closes the polygon.

IMHO this would improve usability a lot as the user can easily combine
drawing and clicking to create a polygon. This feature could be made optional
via L.FreeDraw.Options.

Multiple FreeDraw instances

The problem: How to setup multiple FreeDraw instances to draw polygons in several colors (having a toolbar with e.g. buttons blue and pink to draw blue or pink FreeDraw polygons)

The basics: I know how to configure the according color of the drawing line, polygon stroke and polygon fill if I use only ONE freedraw control (and I know how to connect it to a button).

Get it working for several polygon colors:

  • I created several FreeDraw instances and connected each of them to a button
  • I used the method setIconClassName to configure the elbow colors separately.
  • I then added an option which allows to configure the class name provided in https://github.com/Wildhoney/Leaflet.FreeDraw/blob/master/dist/leaflet.freedraw-src.js#L205
    • This allows me to define stroke and fill color for the several polygon types separately (should this option maybe be added to Leaflet.FreeDraw in general?)

What works
I can then click the blue button and draw a blue polygon with blue elbows.
Afterwards I click the pink button to draw a pink polygon with pink elbows.

The problem
The second polygon draws as expected BUT the elbow colors of the first polygon change from blue to pink as well. The first polygon needs to stay blue of course.

I already debugged everything again and again but didn't find the reason for that.
I suppose that the elbows are somehow stored in a global context (maybe at the map directly?)
but I don't know where and how to fix it.

Do you have any idea? Thanks in advance!

Option destroyPreviousPolygon is not working

Don't know if it is a navigator issue (i'm using JavaFX WebEngine navigator embedded in an app), but option destroyPreviousPolygon is not working. By debugging, i realized that, in function createPolygon(latLngs, forceCreation),:

    /**
     * @method createPolygon
     * @param latLngs {L.LatLng[]}
     * @param [forceCreation=false] {Boolean}
     * @return {L.Polygon|Boolean}
     */
    createPolygon: function createPolygon(latLngs, forceCreation) {

        if (!this.options.multiplePolygons && this.getPolygons(true).length >= 1) {

            if (this.options.destroyPrevious) {

                // Destroy the current polygon and then draw the current polygon.
                this.silently(this.clearPolygons);

this.getPolygons(true).length is always returning 0. Behaviour is the expected one if I replace that by this.polygons.length

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.