diff --git a/README.md b/README.md
index d06465e4..06c8d5f4 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@
Leaflet Plugin For Creating And Editing Geometry Layers
Draw, Edit, Drag, Cut, Snap and Pin Layers
- Supports Markers, CircleMarkers, Polylines, Polygons, Circles, Rectangles, LayerGroups, GeoJSON and MultiPolygons
+ Supports Markers, CircleMarkers, Polylines, Polygons, Circles, Rectangles, ImageOverlays, LayerGroups, GeoJSON and MultiPolygons
@@ -111,12 +111,25 @@ their options when creating them. Example:
L.marker([51.50915, -0.096112], { pmIgnore: true }).addTo(map);
```
+Enable leaflet-geoman on an ignored layer:
+```js
+layer.options.pmIgnore = false;
+L.PM.reInitLayer(layer);
+```
+If `Opt-In` (look below) is `true`, a layers `pmIgnore` property has to be set to `false` to get initiated.
+
+
##### Opt-In
If you want to use leaflet-geoman as opt-in, call the following function right after importing:
```js
-L.PM.initialize({ optIn: true });
+L.PM.setOptIn(true);
+```
+
+And to disable it:
+```js
+L.PM.setOptIn(false);
```
All layers will be ignored by leaflet-geoman, unless you specify `pmIgnore: false` on a layer:
@@ -195,18 +208,18 @@ map.pm.Draw.getShapes();
The following methods are available on `map.pm`:
-| Method | Returns | Description |
-| :---------------------------- | :-------- | :----------------------------------------------------------------------- |
-| enableDraw(`shape`,`options`) | - | Enable Drawing Mode with the passed shape. |
-| disableDraw(`shape`) | - | Disable Drawing Mode. The passed shape is optional. |
-| Draw.getShapes() | `Array` | Array of available shapes. |
-| Draw.getActiveShape() | `String` | Returns the active shape. |
-| globalDrawModeEnabled() | `Boolean` | Returns `true` if global draw mode is enabled. `false` when disabled. |
-| setPathOptions(`options`) | - | Customize the style of the drawn layer. |
-| setGlobalOptions(`options`) | - | Set drawing options. |
-| getGlobalOptions() | `Object` | Returns the global options. |
-| getGeomanLayers() | `Array` | Returns all Geoman layers on the map. |
-| getGeomanDrawLayers() | `Array` | Returns all drawn Geoman layers on the map. |
+| Method | Returns | Description |
+| :---------------------------- | :-------- | :---------------------------------------------------------------------------------------------- |
+| enableDraw(`shape`,`options`) | - | Enable Drawing Mode with the passed shape. |
+| disableDraw(`shape`) | - | Disable Drawing Mode. The passed shape is optional. |
+| Draw.getShapes() | `Array` | Array of available shapes. |
+| Draw.getActiveShape() | `String` | Returns the active shape. |
+| globalDrawModeEnabled() | `Boolean` | Returns `true` if global draw mode is enabled. `false` when disabled. |
+| setPathOptions(`options`) | - | Customize the style of the drawn layer. |
+| setGlobalOptions(`options`) | - | Set drawing options. |
+| getGlobalOptions() | `Object` | Returns the global options. |
+| getGeomanLayers(`Boolean`) | `Array` | Returns all Geoman layers on the map as array. Pass `true` to get a L.FeatureGroup. |
+| getGeomanDrawLayers(`Boolean`)| `Array` | Returns all drawn Geoman layers on the map as array. Pass `true` to get a L.FeatureGroup. |
See the available options in the table below.
@@ -222,8 +235,22 @@ See the available options in the table below.
| cursorMarker | `true` | show a marker at the cursor |
| finishOn | `null` | leaflet layer event to finish the drawn shape, like `'dblclick'`. [Here's a list](http://leafletjs.com/reference-1.2.0.html#interactive-layer-click). |
| markerStyle | `{ draggable: true }` | [leaflet marker options](https://leafletjs.com/reference-1.4.0.html#marker-icon) (only for drawing markers). |
+| hideMiddleMarkers | `false` | hide the middle Markers in edit mode from Polyline and Polygon. |
+| minRadiusCircle | `null` | set the min radius of a `Circle`. |
+| maxRadiusCircle | `null` | set the max radius of a `Circle`. |
+| minRadiusCircleMarker | `null` | set the min radius of a `CircleMarker` when editable is active. |
+| maxRadiusCircleMarker | `null` | set the max radius of a `CircleMarker` when editable is active. |
| editable | `false` | makes a `CircleMarker` editable like a `Circle` |
-| hideMiddleMarkers | `false` | hide the middle Markers in edit mode from Polyline and Polygon. |
+| markerEditable | `true` | Markers and CircleMarkers are editable during the draw-session (you can drag them around immediately after drawing them) |
+| continueDrawing | `false` / `true` | Draw-Mode stays enabled after finishing a layer to immediately draw the next layer. Defaults to `true` for Markers and CircleMarkers and `false` for all other layers. | |
+
+
+
+This options are only available for the global options:
+
+| Option | Default | Description |
+| :-------------------- | :------------------------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------- |
+| layerGroup | `map` | add the created layers to a layergroup instead to the map. |
You can listen to map events to hook into the drawing procedure like this:
@@ -328,6 +355,7 @@ The following events are available on a layer instance:
| pm:vertexadded | `e` | Fired when a vertex is added | `layer`, `indexPath`, `latlng`, `marker`, `shape` |
| pm:vertexremoved | `e` | Fired when a vertex is removed | `layer`, `indexPath`, `marker`, `shape` |
| pm:markerdragstart | `e` | Fired when dragging of a marker which corresponds to a vertex starts | `layer`, `indexPath`, `markerEvent`, `shape` |
+| pm:markerdrag | `e` | Fired when dragging a vertex-marker | `layer`, `indexPath`, `markerEvent`, `shape` |
| pm:markerdragend | `e` | Fired when dragging of a vertex-marker ends | `layer`, `indexPath`, `markerEvent`, `shape` |
| pm:snapdrag | `e` | Fired during a marker move/drag. Payload includes info about involved layers and snapping calculation| `shape`, `distance`, `layer` = `workingLayer`, `marker`, `layerInteractedWith`, `segment`, `snapLatLng` |
| pm:snap | `e` | Fired when a vertex-marker is snapped to another vertex. Also fired on the marker itself. | `shape`, `distance`, `layer` = `workingLayer`, `marker`, `layerInteractedWith`, `segment`, `snapLatLng` |
@@ -508,19 +536,20 @@ map.pm.setGlobalOptions({ pinning: true, limitMarkersToCount: 15, limitMarkersCo
The following options are available globally and apply when going into global edit mode.
-| Option | Default | Description |
-| :------------------------ | :------ | :-------------------------------------------------------------------------------------------------------- |
-| snappable | `true` | Enable snapping to other layers vertices for precision drawing. Can be disabled by holding the `ALT` key. |
-| snapDistance | `20` | The distance to another vertex when a snap should happen. |
-| pinning | `false` | Pin shared vertices/markers together during edit ⭐. [Details](#pinning) |
-| allowSelfIntersection | `true` | Allow/Disallow self-intersections on polygons and polylines. |
-| preventMarkerRemoval | `false` | Disable the removal of markers/vertexes via right click. |
-| limitMarkersToCount | `-1` | Shows only `n` markers per layer closest to the cursor. Use `-1` for no limit |
-| limitMarkersCountGlobally | `false` | Activates `limitMarkersToCount` across layers on the entire map, not just per layer ⭐ |
-| limitMarkersToZoom | `-1` | Shows markers when under the given zoom level ⭐ |
-| limitMarkersToViewport | `false` | Shows only markers in the viewport ⭐ |
-| limitMarkersToClick | `false` | Shows markers only after the layer was clicked ⭐ |
-| editable | `false` | Makes a `CircleMarker` editable like a `Circle` |
+| Option | Default | Description |
+| :------------------------ | :------ | :-------------------------------------------------------------------------------------------------------------------------- |
+| snappable | `true` | Enable snapping to other layers vertices for precision drawing. Can be disabled by holding the `ALT` key. |
+| snapDistance | `20` | The distance to another vertex when a snap should happen. |
+| pinning | `false` | Pin shared vertices/markers together during edit ⭐. [Details](#pinning) |
+| allowSelfIntersection | `true` | Allow/Disallow self-intersections on polygons and polylines. |
+| preventMarkerRemoval | `false` | Disable the removal of markers/vertexes via right click. |
+| limitMarkersToCount | `-1` | Shows only `n` markers per layer closest to the cursor. Use `-1` for no limit |
+| limitMarkersCountGlobally | `false` | Activates `limitMarkersToCount` across layers on the entire map, not just per layer ⭐ |
+| limitMarkersToZoom | `-1` | Shows markers when under the given zoom level ⭐ |
+| limitMarkersToViewport | `false` | Shows only markers in the viewport ⭐ |
+| limitMarkersToClick | `false` | Shows markers only after the layer was clicked ⭐ |
+| editable | `false` | Makes a `CircleMarker` editable like a `Circle` |
+| snappingOrder | `Array` | Prioritize the order of snapping. Default: `['Marker','CircleMarker','Circle','Line','Polygon','Rectangle']` |
Some details about a few more powerful options:
@@ -550,7 +579,7 @@ Change the language of user-facing copy in leaflet-geoman
map.pm.setLang('de');
```
-Currently available languages are `en`, `de`, `it`, `ru`, `ro`, `es`, `fr`, `pt_br`, `id`, `zh`, `nl`, `el`, `pl`, `sv` and `hu`.
+Currently available languages are `en`, `de`, `it`, `ru`, `ro`, `es`, `fr`, `pt_br`, `id`, `zh`, `zh_tw`, `nl`, `el`, `pl`, `sv`, `da` and `hu`.
To add translations to the plugin, you can add [a translation file](src/assets/translations) via Pull Request.
You can also provide your own custom translations.
@@ -701,14 +730,15 @@ map.pm.Toolbar.createCustomControl(options)
| Option | Default | Description |
| :------------ | :---------- | :----------------------------------------------------------------------------------------------- |
-| name | Required | Name of the control |
-| block | '' | block of the control. `draw`, `edit`, `options`⭐, `custom` |
-| title | '' | Text showing when you hover the control |
-| className | '' | CSS class with the Icon |
-| onClick | - | Function fired when clicking the control |
-| afterClick | - | Function fired after clicking the control |
-| actions | [ ] | Action that appears as tooltip. Look under [actions](#actions) for more information |
-| toggle | true | Control can be toggled |
+| name | Required | Name of the control |
+| block | '' | block of the control. `draw`, `edit`, `options`⭐, `custom` |
+| title | '' | Text showing when you hover the control |
+| className | '' | CSS class with the Icon |
+| onClick | - | Function fired when clicking the control |
+| afterClick | - | Function fired after clicking the control |
+| actions | [ ] | Action that appears as tooltip. Look under [actions](#actions) for more information |
+| toggle | true | Control can be toggled |
+| disabled | false | Control is disabled |
**Inherit from an Existing Control**
@@ -764,10 +794,20 @@ The following methods are available on `map.pm.Toolbar`:
| Method | Returns | Description |
| :------------------------------------------ | :-------- | :------------------------------------------------------------------------------------------------------------ |
| createCustomControl(`options`) | - | To add a custom Control to the Toolbar. |
-| copyDrawControl(`instance`, `options`) | `Object` | Creates a copy of a draw Control. Returns the `drawInstance` and the `control`. |
-| changeActionsOfControl(`name`, `actions`) | - | Change the actions of an existing button. |
+| copyDrawControl(`instance`, `options`) | `Object` | Creates a copy of a draw Control. Returns the `drawInstance` and the `control`. |
+| changeActionsOfControl(`name`, `actions`) | - | Change the actions of an existing button. |
| changeControlOrder(`shapes`) | - | Change the order of the controls in the Toolbar. You can pass all shapes and `Edit`, `Drag`, `Removal`, `Cut` |
| getControlOrder() | `Array` | Get the current order of the controls. |
+| setButtonDisabled(`name`, `Boolean`) | - | Enable / disable a button. |
+
+The following events are available on a map instance:
+
+| Event | Params | Description | Output |
+| :------------- | :----- | :---------------------------------------- | :--------------------------------------------------- |
+| pm:buttonclick | `e` | Fired when a Toolbar button is clicked | `btnName`, `button` |
+| pm:actionclick | `e` | Fired when a Toolbar action is clicked | `text`, `action`, `btnName`, `button` |
+
+
### Feature Requests
diff --git a/cypress/fixtures/FeatureCollectionEventFire.json b/cypress/fixtures/FeatureCollectionEventFire.json
new file mode 100644
index 00000000..bdf678de
--- /dev/null
+++ b/cypress/fixtures/FeatureCollectionEventFire.json
@@ -0,0 +1,29 @@
+{
+ "type":"FeatureCollection",
+ "features":[
+ {
+ "type":"Feature",
+ "geometry":{
+ "type":"MultiPoint",
+ "coordinates":[
+ [
+ 32.83604819,
+ 40.0169319
+ ]
+ ]
+ }
+ },
+ {
+ "type":"Feature",
+ "geometry":{
+ "type":"MultiPoint",
+ "coordinates":[
+ [
+ 32.8298979,
+ 40.02567418
+ ]
+ ]
+ }
+ }
+ ]
+}
diff --git a/cypress/integration/circle.spec.js b/cypress/integration/circle.spec.js
index b5548fef..788d3b42 100644
--- a/cypress/integration/circle.spec.js
+++ b/cypress/integration/circle.spec.js
@@ -84,4 +84,157 @@ describe('Draw Circle', () => {
});
});
});
+
+ it('enable continueDrawing', () => {
+ cy.window().then(({ map }) => {
+ map.pm.setGlobalOptions({continueDrawing: true});
+ });
+
+ cy.toolbarButton('circle')
+ .click()
+ .closest('.button-container')
+ .should('have.class', 'active');
+
+ // draw first circle
+ cy.get(mapSelector)
+ .click(200, 200)
+ .click(250, 250);
+
+ // draw with continueDrawing: ture the second circle
+ cy.get(mapSelector)
+ .click(300, 200)
+ .click(350, 250);
+
+ cy.toolbarButton('edit').click();
+ cy.hasVertexMarkers(4);
+ });
+
+ it('set max radius of circle', () => {
+ let handFinish = false;
+
+ cy.toolbarButton('circle')
+ .click()
+ .closest('.button-container')
+ .should('have.class', 'active');
+
+ cy.window().then(({ map, L }) => {
+ L.marker(map.getCenter()).addTo(map);
+ map.pm.setGlobalOptions({
+ minRadiusCircle: 500,
+ maxRadiusCircle: 1500,
+ });
+ cy.get(mapSelector)
+ .click(250,200)
+ .click(620,190)
+ .then(() => {
+ map.eachLayer(layer => {
+ if (layer instanceof L.Circle) {
+ expect(layer.getRadius()).to.equal(1500);
+ }
+ });
+ });
+ });
+
+ cy.toolbarButton('edit')
+ .click()
+ .closest('.button-container')
+ .should('have.class', 'active');
+
+ cy.window().then(({ Hand, map, L }) => {
+ const handMarker = new Hand({
+ timing: 'frame',
+ onStop: ()=>{
+ map.eachLayer(layer => {
+ if (layer instanceof L.Circle) {
+ expect(true).to.equal(layer.getRadius() < 1500);
+ }
+ });
+ const handMarker2 = new Hand({
+ timing: 'frame',
+ onStop: () => {
+ handFinish = true;
+ map.eachLayer(layer => {
+ if (layer instanceof L.Circle) {
+ expect(true).to.equal(layer.getRadius() >= 1495 && layer.getRadius() < 1505);
+ }
+ });
+ }
+ });
+ const toucherMarker2 = handMarker2.growFinger('mouse');
+ toucherMarker2.wait(100).moveTo(317,198, 100).down().wait(500).moveTo(500,198, 600).up().wait(100)
+ }
+ });
+ const toucherMarker = handMarker.growFinger('mouse');
+ toucherMarker.wait(100).moveTo(379,198, 100).down().wait(500).moveTo(317,198, 400).up().wait(100)
+
+ // wait until hand is finished
+ cy.waitUntil(() => cy.window().then(() => handFinish)).then( ()=> {
+ expect(handFinish).to.equal(true);
+ });
+ });
+
+ });
+ it('set min radius of circle', () => {
+ let handFinish = false;
+
+ cy.toolbarButton('circle')
+ .click()
+ .closest('.button-container')
+ .should('have.class', 'active');
+
+ cy.window().then(({ map, L }) => {
+ L.marker(map.getCenter()).addTo(map);
+ map.pm.setGlobalOptions({
+ minRadiusCircle: 1500,
+ maxRadiusCircle: 3000,
+ });
+ cy.get(mapSelector)
+ .click(250,200)
+ .click(300,190)
+ .then(() => {
+ map.eachLayer(layer => {
+ if (layer instanceof L.Circle) {
+ expect(layer.getRadius()).to.equal(1500);
+ }
+ });
+ });
+ });
+
+ cy.toolbarButton('edit')
+ .click()
+ .closest('.button-container')
+ .should('have.class', 'active');
+
+ cy.window().then(({ Hand, map, L }) => {
+ const handMarker = new Hand({
+ timing: 'frame',
+ onStop: ()=>{
+ map.eachLayer(layer => {
+ if (layer instanceof L.Circle) {
+ expect(true).to.equal(layer.getRadius() > 1500)
+ }
+ });
+ const handMarker2 = new Hand({
+ timing: 'frame',
+ onStop: () =>{
+ handFinish = true;
+ map.eachLayer(layer => {
+ if (layer instanceof L.Circle) {
+ expect(true).to.equal(layer.getRadius() >= 1495 && layer.getRadius() < 1505);
+ }
+ });
+ }
+ });
+ const toucherMarker2 = handMarker2.growFinger('mouse');
+ toucherMarker2.wait(200).moveTo(490,198, 100).down().wait(500).moveTo(317,198, 600).up().wait(100)
+ }
+ });
+ const toucherMarker = handMarker.growFinger('mouse');
+ toucherMarker.wait(100).moveTo(379,198, 100).down().wait(500).moveTo(500,198, 400).up().wait(100)
+ // wait until hand is finished
+ cy.waitUntil(() => cy.window().then(() => handFinish)).then( ()=> {
+ expect(handFinish).to.equal(true);
+ });
+ });
+ });
});
diff --git a/cypress/integration/circlemarker.spec.js b/cypress/integration/circlemarker.spec.js
index c7ee36c5..cd681901 100644
--- a/cypress/integration/circlemarker.spec.js
+++ b/cypress/integration/circlemarker.spec.js
@@ -44,55 +44,53 @@ describe('Draw Circle Marker', () => {
createMarkers();
});
-
it('handles 6k circle markers in under 1 sec', () => {
+ cy.toolbarButton('circle-marker').click();
- cy.toolbarButton('circle-marker')
- .click()
-
- cy.get(mapSelector)
- .click(150, 250)
+ cy.get(mapSelector).click(150, 250);
cy.testLayerAdditionPerformance();
});
-
-
it('correctly disables drag', () => {
-
createMarkers();
cy.window().then(({ map, L }) => {
- map.eachLayer((layer) => {
+ map.eachLayer(layer => {
if (layer instanceof L.CircleMarker) {
- assert.isFalse(L.DomUtil.hasClass(layer._path, 'leaflet-pm-draggable'), 'not draggable')
+ assert.isFalse(
+ L.DomUtil.hasClass(layer._path, 'leaflet-pm-draggable'),
+ 'not draggable'
+ );
}
- })
+ });
});
- cy.toolbarButton('edit')
- .click()
+ cy.toolbarButton('edit').click();
cy.window().then(({ map, L }) => {
- map.eachLayer((layer) => {
+ map.eachLayer(layer => {
if (layer instanceof L.CircleMarker) {
- assert.isTrue(L.DomUtil.hasClass(layer._path, 'leaflet-pm-draggable'), 'draggable')
+ assert.isTrue(
+ L.DomUtil.hasClass(layer._path, 'leaflet-pm-draggable'),
+ 'draggable'
+ );
}
- })
+ });
});
- cy.toolbarButton('edit')
- .click()
+ cy.toolbarButton('edit').click();
cy.window().then(({ map, L }) => {
- map.eachLayer((layer) => {
+ map.eachLayer(layer => {
if (layer instanceof L.CircleMarker) {
- assert.isFalse(L.DomUtil.hasClass(layer._path, 'leaflet-pm-draggable'), 'not draggable')
+ assert.isFalse(
+ L.DomUtil.hasClass(layer._path, 'leaflet-pm-draggable'),
+ 'not draggable'
+ );
}
- })
+ });
});
-
-
});
it('deletes all circle markers', () => {
@@ -116,10 +114,9 @@ describe('Draw Circle Marker', () => {
cy.hasCircleLayers(0);
});
-
it('draw a CircleMarker like a Circle', () => {
- cy.window().then(({ map}) => {
- map.pm.setGlobalOptions({editable: true});
+ cy.window().then(({ map }) => {
+ map.pm.setGlobalOptions({ editable: true, continueDrawing: false });
});
cy.toolbarButton('circle-marker')
@@ -133,7 +130,6 @@ describe('Draw Circle Marker', () => {
cy.hasCircleLayers(1);
-
cy.toolbarButton('edit')
.click()
.closest('.button-container')
@@ -141,4 +137,187 @@ describe('Draw Circle Marker', () => {
cy.hasVertexMarkers(2);
});
+ it('snapping to CircleMarker with pmIgnore:true', () => {
+ cy.window().then(({ map, L }) => {
+ L.circleMarker(map.getCenter(), { pmIgnore: true }).addTo(map);
+ });
+
+ cy.toolbarButton('rectangle')
+ .click()
+ .closest('.button-container')
+ .should('have.class', 'active');
+
+ cy.get(mapSelector)
+ .click(200, 200)
+ .click(400, 350);
+
+ cy.toolbarButton('edit')
+ .click()
+ .closest('.button-container')
+ .should('have.class', 'active');
+
+ cy.hasVertexMarkers(4);
+ });
+
+ it('disable continueDrawing', () => {
+ cy.window().then(({ map }) => {
+ map.pm.setGlobalOptions({ continueDrawing: false });
+ });
+
+ cy.toolbarButton('circle-marker').click();
+ cy.get(mapSelector).click(191, 216);
+
+ cy.get(mapSelector).click(350, 350);
+
+ cy.toolbarButton('circle-marker')
+ .closest('.button-container')
+ .should('have.not.class', 'active');
+
+ cy.toolbarButton('edit').click();
+ cy.hasLayers(3);
+ });
+
+ it('disable markerEditable', () => {
+ cy.window().then(({ map }) => {
+ map.pm.setGlobalOptions({ markerEditable: false });
+ });
+
+ cy.toolbarButton('circle-marker').click();
+ cy.get(mapSelector).click(191, 216);
+
+ cy.window().then(({ map }) => {
+ const marker = map.pm.getGeomanDrawLayers()[0];
+ const enabled = marker.pm.enabled();
+ expect(enabled).to.equal(false);
+ });
+ });
+
+ it('enable markerEditable but disable MarkerRemoval', () => {
+ cy.window().then(({ map }) => {
+ map.pm.setGlobalOptions({
+ markerEditable: true,
+ preventMarkerRemoval: true,
+ });
+ });
+
+ cy.toolbarButton('circle-marker').click();
+ cy.get(mapSelector).click(191, 216);
+
+ cy.window().then(({ map }) => {
+ const marker = map.pm.getGeomanDrawLayers()[0];
+ const enabled = marker.pm.enabled();
+ expect(enabled).to.equal(true);
+ });
+
+ cy.get(mapSelector).rightclick(191, 214);
+
+ cy.hasLayers(5);
+ });
+
+ it('set max radius of circleMarker', () => {
+ cy.toolbarButton('circle-marker')
+ .click()
+ .closest('.button-container')
+ .should('have.class', 'active');
+
+ cy.window().then(({ map, L }) => {
+ L.marker(map.getCenter()).addTo(map);
+ map.pm.setGlobalOptions({
+ minRadiusCircleMarker: 50,
+ maxRadiusCircleMarker: 150,
+ editable: true,
+ });
+ cy.get(mapSelector)
+ .click(250, 200)
+ .click(400, 190)
+ .then(() => {
+ const layers = map.pm.getGeomanDrawLayers();
+ layers.forEach(layer => {
+ if (layer instanceof L.CircleMarker) {
+ expect(layer.getRadius()).to.equal(150);
+ }
+ });
+ });
+ });
+ });
+ it('set min radius of circleMarker', () => {
+ let handFinish = false;
+
+ cy.toolbarButton('circle-marker')
+ .click()
+ .closest('.button-container')
+ .should('have.class', 'active');
+
+ cy.window().then(({ map, L }) => {
+ L.marker(map.getCenter()).addTo(map);
+ map.pm.setGlobalOptions({
+ minRadiusCircleMarker: 150,
+ maxRadiusCircleMarker: 300,
+ editable: true,
+ });
+ cy.get(mapSelector)
+ .click(250, 200)
+ .click(300, 200)
+ .then(() => {
+ const layers = map.pm.getGeomanDrawLayers();
+ layers.forEach(layer => {
+ if (layer instanceof L.CircleMarker) {
+ expect(layer.getRadius()).to.equal(150);
+ }
+ });
+ });
+ });
+ cy.toolbarButton('edit')
+ .click()
+ .closest('.button-container')
+ .should('have.class', 'active');
+
+ cy.window().then(({ Hand, map, L }) => {
+ const handMarker = new Hand({
+ timing: 'frame',
+ onStop: () => {
+ map.eachLayer(layer => {
+ if (layer instanceof L.CircleMarker) {
+ expect(true).to.equal(layer.getRadius() > 150);
+ }
+ });
+ const handMarker2 = new Hand({
+ timing: 'frame',
+ onStop: () => {
+ handFinish = true;
+ map.eachLayer(layer => {
+ if (layer instanceof L.CircleMarker) {
+ expect(true).to.equal(
+ layer.getRadius() >= 145 && layer.getRadius() < 155
+ );
+ }
+ });
+ },
+ });
+ const toucherMarker2 = handMarker2.growFinger('mouse');
+ toucherMarker2
+ .wait(200)
+ .moveTo(490, 198, 100)
+ .down()
+ .wait(500)
+ .moveTo(317, 198, 600)
+ .up()
+ .wait(100);
+ },
+ });
+ const toucherMarker = handMarker.growFinger('mouse');
+ toucherMarker
+ .wait(100)
+ .moveTo(400, 198, 100)
+ .down()
+ .wait(500)
+ .moveTo(500, 198, 400)
+ .up()
+ .wait(100);
+ // wait until hand is finished
+ cy.waitUntil(() => cy.window().then(() => handFinish)).then(() => {
+ expect(handFinish).to.equal(true);
+ });
+ });
+ });
});
diff --git a/cypress/integration/events.spec.js b/cypress/integration/events.spec.js
index d0b96b8f..a008a4e3 100644
--- a/cypress/integration/events.spec.js
+++ b/cypress/integration/events.spec.js
@@ -345,12 +345,14 @@ describe('Events', () => {
it('Events while editing: pm:edit,pm:update,pm:enable,pm:disable,pm:vertexadded,pm:vertexremoved', () => {
let calledevent = "";
+ let calledeventArr = [];
cy.window().then(({map}) => {
function logEvent(e){
console.log(e.type)
calledevent = e.type;
+ calledeventArr[e.type] = e.type;
}
map.on("pm:create",({layer}) => {
@@ -414,8 +416,9 @@ describe('Events', () => {
map.pm.disableGlobalEditMode();
}).then(()=>{
cy.wait(100);
- expect(calledevent).to.equal("pm:update");
+ expect(calledeventArr["pm:update"]).to.equal("pm:update");
calledevent = "";
+ calledeventArr = [];
});
cy.window().then(({map}) => {
@@ -520,7 +523,49 @@ describe('Events', () => {
});
-/*
- */
+ it('snappingOrder', () => {
+
+ let event = "";
+ cy.window().then(({ map}) => {
+ map.on('pm:drawstart',(e)=>{
+ e.workingLayer.on('pm:snap', (x)=>{event=x});
+ });
+
+ map.pm.setGlobalOptions({snappingOrder: ['Marker']});
+ });
+
+ cy.window().then(() => {
+ cy.toolbarButton('marker').click();
+ cy.get(mapSelector)
+ .click(200, 250);
+
+ cy.toolbarButton('circle-marker').click();
+ cy.get(mapSelector)
+ .click(200, 250);
+
+ cy.toolbarButton('marker').click();
+ cy.get(mapSelector)
+ .trigger("mousemove", 200, 250, { which: 1 })
+ });
+ cy.window().then(() => {
+ const shape = event.layerInteractedWith.pm._shape;
+ expect(shape).to.eq("Marker");
+ });
+
+
+ cy.window().then(({map}) => {
+ map.pm.setGlobalOptions({snappingOrder: ['CircleMarker']});
+
+ map.pm.enableDraw('Marker');
+
+ cy.get(mapSelector)
+ .trigger("mousemove", 200, 150, { which: 1 })
+ .trigger("mousemove", 200, 250, { which: 1 })
+ });
+ cy.window().then(() => {
+ const shape = event.layerInteractedWith.pm._shape;
+ expect(shape).to.eq("CircleMarker");
+ });
+ });
});
diff --git a/cypress/integration/globalmodes.spec.js b/cypress/integration/globalmodes.spec.js
index d6e26ca2..e124cd3c 100644
--- a/cypress/integration/globalmodes.spec.js
+++ b/cypress/integration/globalmodes.spec.js
@@ -311,4 +311,20 @@ describe('Modes', () => {
).to.equal(0);
});
});
+ it('re-enable layers that added while in globaleditmode', () => {
+
+ cy.window().then(({ map, L }) => {
+ map.pm.enableGlobalEditMode();
+
+ const json = JSON.parse("{\"type\":\"Feature\",\"properties\":{},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-74.058559,40.718564],[-74.058559,40.726045],[-74.03959,40.726045],[-74.03959,40.718564],[-74.058559,40.718564]]]}}");
+ const json2 = JSON.parse("{\"type\":\"Feature\",\"properties\":{},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-74.035277,40.703719],[-74.035277,40.712633],[-74.017596,40.712633],[-74.017596,40.703719],[-74.035277,40.703719]]]}}");
+ const p2 = L.geoJson(json).addTo(map);
+ L.geoJson(json2).addTo(map);
+
+ map.fitBounds(p2.getBounds());
+ map.setZoom(13);
+ });
+ cy.hasVertexMarkers(8);
+
+ });
});
diff --git a/cypress/integration/imageoverlay.spec.js b/cypress/integration/imageoverlay.spec.js
new file mode 100644
index 00000000..fdad2769
--- /dev/null
+++ b/cypress/integration/imageoverlay.spec.js
@@ -0,0 +1,32 @@
+describe('Opens Testing Environment', () => {
+ const mapSelector = '#map';
+
+ it('Snap to ImageOverlay', () => {
+ let eventcalled = "";
+ cy.window().then(({ map, L }) => {
+ map.setView([18.74469, 72.1258],10);
+ const icon = 'https://camo.githubusercontent.com/33fa9a94048274f81a806631ca881a55c2aa8f0a/68747470733a2f2f66696c652d6a787a796a67717775742e6e6f772e73682f';
+ L.imageOverlay(icon, [[18.74469, 72.1258], [18.94469, 72.3258]], {interactive: true})
+ .addTo(map);
+
+ map.on('pm:drawstart',(e)=>{
+ const layer = e.workingLayer;
+ layer.on('pm:snap', (x)=>eventcalled = x.type);
+ });
+ });
+
+ cy.toolbarButton('polygon')
+ .click();
+
+ cy.window().then(({map}) => {
+ const point = map.latLngToContainerPoint([18.74469, 72.1258]);
+ cy.get(mapSelector)
+ .click(point);
+ });
+
+ cy.window().then(({}) => {
+ expect(eventcalled).to.equal("pm:snap");
+ });
+
+ });
+});
diff --git a/cypress/integration/layergroup.spec.js b/cypress/integration/layergroup.spec.js
index 081ba2bb..f49d908f 100644
--- a/cypress/integration/layergroup.spec.js
+++ b/cypress/integration/layergroup.spec.js
@@ -1,5 +1,5 @@
describe('Edit LayerGroup', () => {
- // const mapSelector = '#map';
+ const mapSelector = '#map';
it('correctly enables geojson featureCollection', () => {
cy.drawShape('FeatureCollectionWithCircles');
@@ -43,8 +43,7 @@ describe('Edit LayerGroup', () => {
});
it('supports clearLayers', () => {
-
- cy.window().then(({ L, map }) => {
+ cy.window().then(({L, map}) => {
const featureGroup = new L.FeatureGroup();
featureGroup.addTo(map)
featureGroup.addLayer(new L.Marker([19.04469, 72.9258]));
@@ -53,5 +52,227 @@ describe('Edit LayerGroup', () => {
expect(featureGroup.pm._layers).to.have.lengthOf(0);
});
+ });
+
+ it('adds the created layers to a layergroup', () => {
+ let fg;
+ let fg2;
+
+ // Add layer to group
+ cy.window().then(({L, map}) => {
+ fg = L.featureGroup().addTo(map);
+ fg2 = L.featureGroup().addTo(map);
+ map.on('click', (e) => console.log(map.latLngToContainerPoint(e.latlng)));
+
+ map.pm.setGlobalOptions({layerGroup: fg});
+
+ cy.toolbarButton('rectangle')
+ .click()
+ .closest('.button-container')
+ .should('have.class', 'active');
+
+ cy.get(mapSelector)
+ .click(200, 200)
+ .click(400, 350);
+
+ cy.hasLayers(5);
+ });
+
+ // check if layer is on group and will be removed from map, when group is removed
+ cy.window().then(({map}) => {
+ fg.removeFrom(map);
+ cy.hasLayers(3);
+
+ const count = fg.getLayers().length;
+ expect(count).to.equal(1);
+ });
+ // delete layer from group and map
+ cy.window().then(({map}) => {
+ fg.addTo(map);
+
+ cy.toolbarButton('delete')
+ .click();
+
+ cy.get(mapSelector)
+ .click(280, 280);
+ });
+ cy.window().then(() => {
+ const count = fg.getLayers().length;
+ expect(count).to.equal(0);
+ });
+
+ // add layer and then change group
+ cy.window().then(() => {
+ cy.toolbarButton('rectangle')
+ .click();
+
+ cy.get(mapSelector)
+ .click(200, 200)
+ .click(400, 350);
+ });
+ cy.window().then(({map}) => {
+ map.pm.setGlobalOptions({layerGroup: fg2});
+ cy.hasLayers(5);
+
+ cy.toolbarButton('circle')
+ .click();
+ cy.get(mapSelector)
+ .click(200, 200)
+ .click(250, 250);
+ });
+ // delete layer from another (first) group
+ cy.window().then(() => {
+ cy.toolbarButton('delete')
+ .click();
+
+ cy.get(mapSelector)
+ .click(280, 280);
+ });
+ cy.window().then(() => {
+ const count = fg.getLayers().length;
+ expect(count).to.equal(1);
+ const count2 = fg2.getLayers().length;
+ expect(count2).to.equal(1);
+ cy.hasLayers(5);
+ });
+ // delete circle from second group
+ cy.window().then(() => {
+ cy.get(mapSelector)
+ .click(180, 180);
+ cy.toolbarButton('delete')
+ .click();
+ });
+ cy.window().then(() => {
+ const count2 = fg2.getLayers().length;
+ expect(count2).to.equal(0);
+ cy.hasLayers(4);
+ });
})
+
+
+ it('pass the fired event of the layer to group', () => {
+ let fg;
+ let firedEvent = "";
+
+ cy.window().then(({map, L}) => {
+ fg = L.featureGroup();
+ fg.on("pm:cut", (e) => {
+ firedEvent = e.type;
+ });
+
+ map.on('pm:create', (e) => {
+ e.layer.addTo(fg);
+ })
+ });
+ // activate polygon drawing
+ cy.toolbarButton('polygon')
+ .click()
+ .closest('.button-container')
+ .should('have.class', 'active');
+
+ // draw a polygon
+ cy.get(mapSelector)
+ .click(90, 250)
+ .click(150, 50)
+ .click(500, 50)
+ .click(500, 300)
+ .click(300, 350)
+ .click(90, 250);
+
+ // activate cutting drawing
+ cy.toolbarButton('cut')
+ .click()
+ .closest('.button-container')
+ .should('have.class', 'active');
+
+ // draw a polygon to cut
+ cy.get(mapSelector)
+ .click(450, 100)
+ .click(450, 150)
+ .click(400, 150)
+ .click(390, 140)
+ .click(390, 100)
+ .click(450, 100);
+
+ cy.window().then(() => {
+ expect(firedEvent).to.equal('pm:cut');
+ });
+ });
+
+ it('event is fired only once if group has multiple sub-groups with the same layer', () => {
+
+ let firedEventCount = 0;
+ cy.window().then(({map, L}) => {
+ const group = L.featureGroup().addTo(map);
+ const layers = L.featureGroup().addTo(group);
+ const markers = L.featureGroup().addTo(group);
+ const markersChild = L.featureGroup().addTo(markers);
+ L.marker(map.getCenter()).addTo(layers).addTo(markers).addTo(markersChild);
+
+ group.on('pm:enable', () => {
+ firedEventCount += 1
+ });
+ });
+
+ cy.wait(100);
+
+ cy.toolbarButton('edit').click();
+
+ cy.window().then(() => {
+ expect(firedEventCount).to.equal(1);
+ });
+ });
+
+ it('event is fired on every parent group of a layer (once)', () => {
+
+ let firedEventCount = 0;
+ cy.window().then(({map, L}) => {
+ const group = L.featureGroup().addTo(map);
+ const layers = L.featureGroup().addTo(group);
+ const markers = L.featureGroup().addTo(group);
+ const markersChild = L.featureGroup().addTo(markers);
+ L.marker(map.getCenter()).addTo(layers).addTo(markers).addTo(markersChild);
+
+ group.on('pm:enable', () => {
+ firedEventCount += 1
+ });
+ layers.on('pm:enable', () => {
+ firedEventCount += 1
+ });
+ markers.on('pm:enable', () => {
+ firedEventCount += 1
+ });
+ markersChild.on('pm:enable', () => {
+ firedEventCount += 1
+ });
+ });
+
+ cy.wait(100);
+
+ cy.toolbarButton('edit').click();
+
+ cy.window().then(() => {
+ expect(firedEventCount).to.equal(4);
+ });
+ });
+
+ it('event is fired on L.geoJson when it has FeatureCollections', () => {
+ cy.drawShape('FeatureCollectionEventFire');
+
+ let firedEventCount = 0;
+ cy.get('@feature').then(feature => {
+ feature.on('pm:enable', () => {
+ firedEventCount += 1
+ });
+ });
+
+ cy.wait(100);
+
+ cy.toolbarButton('edit').click();
+
+ cy.window().then(() => {
+ expect(firedEventCount).to.equal(2);
+ });
+ });
+
});
diff --git a/cypress/integration/line.spec.js b/cypress/integration/line.spec.js
index 571ea327..21884f44 100644
--- a/cypress/integration/line.spec.js
+++ b/cypress/integration/line.spec.js
@@ -184,4 +184,28 @@ describe('Draw & Edit Line', () => {
cy.hasMiddleMarkers(0);
});
+ it('enable continueDrawing', () => {
+ cy.window().then(({ map }) => {
+ map.pm.setGlobalOptions({continueDrawing: true});
+ });
+
+ cy.toolbarButton('polyline').click();
+
+ // draw a line
+ cy.get(mapSelector)
+ .click(150, 250)
+ .click(160, 50)
+ .click(250, 50)
+ .click(250, 50);
+
+ cy.get(mapSelector)
+ .click(200, 200)
+ .click(250, 250)
+ .click(250, 250);
+
+
+ cy.toolbarButton('edit').click();
+ cy.hasVertexMarkers(5);
+ });
+
});
diff --git a/cypress/integration/marker.spec.js b/cypress/integration/marker.spec.js
index 65cd0687..87151d1b 100644
--- a/cypress/integration/marker.spec.js
+++ b/cypress/integration/marker.spec.js
@@ -95,10 +95,8 @@ describe('Draw Marker', () => {
});
});
-
-
it('calls pm:drag-events on Marker drag', () => {
-
+ let handFinish = false;
let dragstart = false;
let drag = false;
let dragend = false;
@@ -133,12 +131,86 @@ describe('Draw Marker', () => {
expect(dragstart).to.equal(true);
expect(drag).to.equal(true);
expect(dragend).to.equal(true);
+ handFinish = true;
}
});
const toucherMarker = handMarker.growFinger('mouse');
toucherMarker.wait(100).moveTo(150, 240, 100).down().wait(500).moveTo(170, 290, 400).up().wait(100) // Not allowed
});
+
+ // wait until hand is finished
+ cy.waitUntil(() => cy.window().then(() => handFinish)).then( ()=> {
+ expect(handFinish).to.equal(true);
+ });
+ });
+
+
+ it('enabled of Marker is true in edit-mode', () => {
+ cy.toolbarButton('marker').click();
+ cy.get(mapSelector)
+ .click(150, 250);
+ cy.toolbarButton('edit').click();
+
+ cy.window().then(({ map }) => {
+ const marker = map.pm.getGeomanDrawLayers()[0];
+ const enabled = marker.pm.enabled();
+ expect(enabled).to.equal(true);
+ });
+ });
+
+ it('disable continueDrawing', () => {
+ cy.window().then(({ map }) => {
+ map.pm.setGlobalOptions({continueDrawing: false});
+ });
+
+ cy.toolbarButton('marker').click();
+ cy.get(mapSelector)
+ .click(191,216);
+
+ cy.get(mapSelector)
+ .click(350, 350);
+
+
+ cy.toolbarButton('edit').click();
+ cy.hasLayers(2);
+ });
+
+ it('disable markerEditable', () => {
+ cy.window().then(({ map }) => {
+ map.pm.setGlobalOptions({markerEditable: false});
+ });
+
+ cy.toolbarButton('marker').click();
+ cy.get(mapSelector)
+ .click(191,216);
+
+ cy.window().then(({ map }) => {
+ const marker = map.pm.getGeomanDrawLayers()[0];
+ const enabled = marker.pm.enabled();
+ expect(enabled).to.equal(false);
+ });
+ });
+
+ it('enable markerEditable but disable MarkerRemoval', () => {
+ cy.window().then(({ map }) => {
+ map.pm.setGlobalOptions({markerEditable: true, preventMarkerRemoval: true});
+ });
+
+ cy.toolbarButton('marker').click();
+ cy.get(mapSelector)
+ .click(191,216);
+
+ cy.window().then(({ map }) => {
+ const marker = map.pm.getGeomanDrawLayers()[0];
+ const enabled = marker.pm.enabled();
+ expect(enabled).to.equal(true);
+ });
+
+ cy.get(mapSelector)
+ .rightclick(191,214);
+
+ cy.hasLayers(4);
});
});
diff --git a/cypress/integration/polygon.spec.js b/cypress/integration/polygon.spec.js
index 50d54e44..abac2720 100644
--- a/cypress/integration/polygon.spec.js
+++ b/cypress/integration/polygon.spec.js
@@ -22,7 +22,7 @@ describe('Draw & Edit Poly', () => {
it('works without pmIgnore', () => {
cy.window().then(({ L }) => {
- L.PM.initialize({ optIn: false });
+ L.PM.setOptIn(false);
cy.drawShape('MultiPolygon');
});
@@ -33,7 +33,7 @@ describe('Draw & Edit Poly', () => {
it('respects pmIgnore', () => {
cy.window().then(({ L }) => {
- L.PM.initialize({ optIn: false });
+ L.PM.setOptIn(false);
cy.drawShape('MultiPolygon', true);
});
@@ -44,7 +44,7 @@ describe('Draw & Edit Poly', () => {
it('respects optIn', () => {
cy.window().then(({ L }) => {
- L.PM.initialize({ optIn: true });
+ L.PM.setOptIn(true);
cy.drawShape('MultiPolygon');
});
@@ -55,7 +55,7 @@ describe('Draw & Edit Poly', () => {
it('respects pmIgnore with optIn', () => {
cy.window().then(({ L }) => {
- L.PM.initialize({ optIn: true });
+ L.PM.setOptIn(true);
cy.drawShape('MultiPolygon', false);
});
@@ -64,6 +64,57 @@ describe('Draw & Edit Poly', () => {
cy.hasVertexMarkers(8);
});
+ it('respects optIn and reinit layer', () => {
+ cy.window().then(({ L }) => {
+ L.PM.setOptIn(true);
+ cy.drawShape('MultiPolygon').then((poly)=>{
+ cy.hasVertexMarkers(0); //Not allowed because optIn
+ L.PM.setOptIn(false);
+ L.PM.reInitLayer(poly);
+ })
+ });
+ cy.toolbarButton('edit').click();
+
+ cy.hasVertexMarkers(8);
+ });
+
+ it('respects optIn and reinit layer with pmIgnore', () => {
+ cy.window().then(({ L }) => {
+ L.PM.setOptIn(true);
+ cy.drawShape('MultiPolygon',true).then((poly)=>{
+ cy.hasVertexMarkers(0); //Not allowed because optIn
+ L.PM.reInitLayer(poly);//Not allowed because pmIgnore is not false
+ cy.hasVertexMarkers(0);
+ L.PM.setOptIn(false);
+ L.PM.reInitLayer(poly);//Not allowed because pmIgnore is true
+ cy.hasVertexMarkers(0);
+ poly.options.pmIgnore = false;
+ poly.eachLayer((layer)=>{
+ layer.options.pmIgnore = false;
+ });
+ L.PM.reInitLayer(poly);//Allowed because pmIgnore is not true
+ })
+ });
+ cy.toolbarButton('edit').click();
+
+ cy.hasVertexMarkers(8);
+ });
+
+ it('respects optIn and disable optIn', () => {
+ cy.window().then(({ L }) => {
+ L.PM.setOptIn(true);
+ cy.drawShape('MultiPolygon');
+ cy.drawShape('MultiPolygon',false).then(()=>{
+ L.PM.setOptIn(false);
+ cy.drawShape('MultiPolygon');
+ })
+ });
+
+ cy.toolbarButton('edit').click();
+
+ cy.hasVertexMarkers(16);
+ });
+
it('doesnt finish single point polys', () => {
cy.toolbarButton('polygon').click();
@@ -589,26 +640,15 @@ describe('Draw & Edit Poly', () => {
const layer = L.geoJSON(json).getLayers()[0].addTo(map);
const bounds = layer.getBounds();
map.fitBounds(bounds);
- console.log(map.getCenter());
return layer;
})
.as('poly');
cy.get("@poly").then((poly)=>{
+ let handFinish = false;
expect(poly.pm.hasSelfIntersection()).to.equal(true);
- const hand_selfIntersectionTrue = new Hand({
- timing: 'frame',
- onStop () {
- expect(poly.pm.hasSelfIntersection()).to.equal(true);
-
- const toucher_selfIntersectionFalse = hand_selfIntersectionFalse.growFinger('mouse');
- toucher_selfIntersectionFalse.wait(100).moveTo(504, 337, 100).down().wait(500).moveTo(780, 259, 400).up().wait(100) // allowed
- // No intersection anymore
- .moveTo(294, 114, 100).down().wait(500).moveTo(752, 327, 800).up().wait(500) // Not allowed
- }
- });
- var hand_selfIntersectionFalse = new Hand({
+ const handSelfIntersectionFalse = new Hand({
timing: 'frame',
onStop () {
expect(poly.pm.hasSelfIntersection()).to.equal(false);
@@ -617,7 +657,18 @@ describe('Draw & Edit Poly', () => {
const center = map.getCenter();
expect(center.lat).to.equal(48.77492609799526);
expect(center.lng).to.equal(4.847301999999988);
+ handFinish = true;
+ }
+ });
+ const handSelfIntersectionTrue = new Hand({
+ timing: 'frame',
+ onStop () {
+ expect(poly.pm.hasSelfIntersection()).to.equal(true);
+ const toucherSelfIntersectionFalse = handSelfIntersectionFalse.growFinger('mouse');
+ toucherSelfIntersectionFalse.wait(100).moveTo(504, 337, 100).down().wait(500).moveTo(780, 259, 400).up().wait(100) // allowed
+ // No intersection anymore
+ .moveTo(294, 114, 100).down().wait(500).moveTo(752, 327, 800).up().wait(500) // Not allowed
}
});
@@ -625,12 +676,98 @@ describe('Draw & Edit Poly', () => {
map.pm.enableGlobalEditMode({ allowSelfIntersection: false, allowSelfIntersectionEdit: true, });
- const toucher_selfIntersectionTrue = hand_selfIntersectionTrue.growFinger('mouse');
- toucher_selfIntersectionTrue.wait(100).moveTo(294, 114, 100).down().wait(500).moveTo(782, 127, 400).up().wait(100) // Not allowed
+ const toucherSelfIntersectionTrue = handSelfIntersectionTrue.growFinger('mouse');
+ toucherSelfIntersectionTrue.wait(100).moveTo(294, 114, 100).down().wait(500).moveTo(782, 127, 400).up().wait(100) // Not allowed
.moveTo(313, 345, 100).down().wait(500).moveTo(256, 311, 400).up().wait(100) // allowed
.moveTo(317, 252, 100).down().wait(500).moveTo(782, 127, 400).up().wait(500); // allowed
- })
+
+ // wait until hand is finished
+ cy.waitUntil(() => cy.window().then(() => handFinish),{
+ timeout: 9000,
+ }).then( ()=> {
+ expect(handFinish).to.equal(true);
+ });
+
+ });
});
});
+
+ it('no snapping to polygon with no coords', () => {
+ cy.window().then(({ map, L }) => {
+ L.polygon([]).addTo(map);
+ });
+
+ // activate line drawing
+ cy.toolbarButton('polyline')
+ .click()
+ .closest('.button-container')
+ .should('have.class', 'active');
+
+ // draw a line
+ cy.get(mapSelector)
+ .click(150, 250)
+ .click(160, 50)
+ .click(160, 50);
+
+
+ cy.toolbarButton('edit')
+ .click();
+
+ cy.hasVertexMarkers(2);
+ });
+
+ it('don\'t Cut if it has selfIntersection on finish', () => {
+ cy.toolbarButton('polygon')
+ .click();
+
+ cy.get(mapSelector)
+ .click(90, 250)
+ .click(150, 50)
+ .click(500, 50)
+ .click(500, 300)
+ .click(300, 350)
+ .click(90, 250);
+
+
+ cy.toolbarButton('cut')
+ .click();
+
+ // draw a polygon to cut
+ cy.get(mapSelector)
+ .click(200, 100)
+ .click(450, 100)
+ .click(150, 200)
+ .click(450, 200)
+ .click(200, 100);
+
+ cy.toolbarButton('edit')
+ .click();
+
+ cy.hasVertexMarkers(5);
+ });
+
+ it('enable continueDrawing', () => {
+ cy.window().then(({ map }) => {
+ map.pm.setGlobalOptions({continueDrawing: true});
+ });
+
+ cy.toolbarButton('polygon').click();
+
+ // draw a line
+ cy.get(mapSelector)
+ .click(150, 250)
+ .click(160, 50)
+ .click(250, 50)
+ .click(150, 250);
+
+ cy.get(mapSelector)
+ .click(230, 230)
+ .click(250, 250)
+ .click(250, 300)
+ .click(230, 230);
+
+ cy.toolbarButton('edit').click();
+ cy.hasVertexMarkers(6);
+ });
});
diff --git a/cypress/integration/rectangle.spec.js b/cypress/integration/rectangle.spec.js
index dd180522..d2e5d6ec 100644
--- a/cypress/integration/rectangle.spec.js
+++ b/cypress/integration/rectangle.spec.js
@@ -97,5 +97,116 @@ describe('Draw Rectangle', () => {
cy.toolbarButton('edit').click();
cy.hasVertexMarkers(16);
- })
+ });
+
+ it('goes back to blue after self-intersection removed', ()=>{
+ cy.toolbarButton('rectangle').click();
+ cy.get(mapSelector)
+ .click(100,50)
+ .click(700,400);
+
+ cy.toolbarButton('cut').click();
+ cy.get(mapSelector)
+ .click(200,200)
+ .click(250,230)
+ .click(300,250)
+ .click(370,200)
+ .click(200,200);
+
+ cy.toolbarButton('cut').click();
+ cy.get(mapSelector)
+ .click(200,300)
+ .click(250,270)
+ .click(300,250)
+ .click(370,300)
+ .click(200,300);
+
+ cy.toolbarButton('edit').click();
+ cy.hasVertexMarkers(12);
+
+ cy.get(mapSelector).rightclick(300,250);
+
+ cy.window().then(({ map, L }) => {
+ const rect = map.pm.getGeomanDrawLayers()[0];
+ expect(rect.options.color).to.not.equal('red');
+
+ })
+ });
+
+ it('remove empty coord rings', ()=>{
+ cy.toolbarButton('rectangle').click();
+ cy.get(mapSelector)
+ .click(100,50)
+ .click(700,400);
+
+ cy.toolbarButton('cut').click();
+ cy.get(mapSelector)
+ .click(200,200)
+ .click(300,250)
+ .click(370,200)
+ .click(200,200);
+
+
+ cy.toolbarButton('edit').click();
+ cy.hasVertexMarkers(7);
+
+ cy.get(mapSelector).rightclick(300,250);
+
+ cy.window().then(({ map}) => {
+ const rect = map.pm.getGeomanDrawLayers()[0];
+ const geojson = rect.toGeoJSON();
+ const coords = geojson.geometry.coordinates;
+ expect(coords.length).to.equal(1);
+ })
+ });
+
+ it('enable continueDrawing', () => {
+ cy.window().then(({ map }) => {
+ map.pm.setGlobalOptions({continueDrawing: true});
+ });
+
+ cy.toolbarButton('rectangle').click();
+ cy.get(mapSelector)
+ .click(191,216)
+ .click(608,323);
+
+ cy.get(mapSelector)
+ .click(230, 230)
+ .click(350, 350);
+
+
+ cy.toolbarButton('edit').click();
+ cy.hasVertexMarkers(8);
+ });
+
+ it('disable popup on layer while drawing', ()=>{
+ let rect = null;
+ cy.window().then(({ map, L }) => {
+ map.on("pm:create",(e)=>{
+ e.layer.bindPopup('Popup test');
+ if(e.layer instanceof L.Rectangle){
+ rect = e.layer;
+ }
+ });
+ });
+
+ cy.toolbarButton('rectangle').click();
+ cy.get(mapSelector)
+ .click(100,50)
+ .click(700,400);
+
+ cy.toolbarButton('marker').click();
+ cy.get(mapSelector)
+ .click(300,250);
+
+ cy.toolbarButton('edit').click();
+
+ cy.window().then(({ map}) => {
+ const len = map.pm.getGeomanDrawLayers().length;
+ expect(len).to.equal(2);
+
+ const text = rect.getPopup().getContent();
+ expect(text).to.equal('Popup test');
+ })
+ });
});
diff --git a/cypress/integration/toolbar.spec.js b/cypress/integration/toolbar.spec.js
index 866b242f..1f8b066c 100644
--- a/cypress/integration/toolbar.spec.js
+++ b/cypress/integration/toolbar.spec.js
@@ -351,4 +351,36 @@ describe('Testing the Toolbar', () => {
});
});
});
+ it('Listen on pm:buttonclick and pm:actionclick', () => {
+ let eventFired = "";
+ cy.window().then(({map}) => {
+ map.on('pm:buttonclick', ({btnName})=>{eventFired = btnName});
+ map.on('pm:actionclick', ({text})=>{eventFired = text});
+ });
+
+ cy.toolbarButton('polygon').click();
+
+ cy.window().then(() => {
+ expect(eventFired).to.equal('drawPolygon');
+ });
+
+ cy.get('.button-container.active .action-cancel').click();
+
+ cy.window().then(() => {
+ expect(eventFired).to.equal('Cancel');
+ });
+ });
+ it('Disable button', () => {
+ let eventFired = "";
+ cy.window().then(({map}) => {
+ map.on('pm:buttonclick', ({btnName})=>{eventFired = btnName});
+ map.pm.Toolbar.setButtonDisabled('drawPolygon',true);
+ });
+
+ cy.toolbarButton('polygon').click();
+
+ cy.window().then(() => {
+ expect(eventFired).to.not.equal('drawPolygon');
+ });
+ });
});
diff --git a/cypress/support/commands.js b/cypress/support/commands.js
index bbb742aa..1eb047e3 100644
--- a/cypress/support/commands.js
+++ b/cypress/support/commands.js
@@ -1,3 +1,4 @@
+import 'cypress-wait-until';
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
@@ -136,6 +137,7 @@ Cypress.Commands.add('drawShape', (shape, ignore) => {
const layer = L.geoJson(json, { pmIgnore: ignore }).addTo(map);
const bounds = layer.getBounds();
map.fitBounds(bounds);
+ return layer;
});
}
@@ -171,6 +173,19 @@ Cypress.Commands.add('drawShape', (shape, ignore) => {
});
}
+ if (shape === 'FeatureCollectionEventFire') {
+ cy.fixture(shape)
+ .then(json => {
+ //
+ const layer = L.geoJSON(json).addTo(map);
+ const bounds = layer.getBounds();
+ map.fitBounds(bounds);
+
+ return layer;
+ })
+ .as('feature');
+ }
+
if (shape === 'FeatureCollectionWithCircles') {
cy.fixture(shape, ignore)
.then(json => {
diff --git a/demo/customcontrols.js b/demo/customcontrols.js
index ca11b8c1..077cf282 100644
--- a/demo/customcontrols.js
+++ b/demo/customcontrols.js
@@ -30,3 +30,9 @@ map.pm.Draw.RectangleCopy.setPathOptions({color :'green'});
map.pm.Toolbar.changeControlOrder(["RectangleCopy"])
+map.on('pm:actionclick',function (e) {
+ console.log(e)
+})
+map.on('pm:buttonclick',function (e) {
+ console.log(e)
+})
diff --git a/demo/events.js b/demo/events.js
index 3b79846a..ea3792a5 100644
--- a/demo/events.js
+++ b/demo/events.js
@@ -42,6 +42,7 @@ map.on('pm:create',function (e) {
layer.on('pm:vertexadded', logEvent);
layer.on('pm:vertexremoved', logEvent);
layer.on('pm:markerdragstart', logEvent);
+ layer.on('pm:markerdrag', logEvent);
layer.on('pm:markerdragend', logEvent);
layer.on('pm:snap', logEvent);
layer.on('pm:snapdrag', logEvent);
diff --git a/package-lock.json b/package-lock.json
index 6b86cb97..4567e737 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "@geoman-io/leaflet-geoman-free",
- "version": "2.7.0",
+ "version": "2.8.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -1141,6 +1141,108 @@
"@turf/meta": "6.x"
}
},
+ "@turf/bearing": {
+ "version": "5.1.5",
+ "resolved": "https://registry.npmjs.org/@turf/bearing/-/bearing-5.1.5.tgz",
+ "integrity": "sha1-egt5ATbE70eX8CRjBdRcvi0ns/c=",
+ "requires": {
+ "@turf/helpers": "^5.1.5",
+ "@turf/invariant": "^5.1.5"
+ },
+ "dependencies": {
+ "@turf/helpers": {
+ "version": "5.1.5",
+ "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-5.1.5.tgz",
+ "integrity": "sha1-FTQFInq5M9AEpbuWQantmZ/L4M8="
+ },
+ "@turf/invariant": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@turf/invariant/-/invariant-5.2.0.tgz",
+ "integrity": "sha1-8BUP9ykLOFd7c9CIt5MsHuCqkKc=",
+ "requires": {
+ "@turf/helpers": "^5.1.5"
+ }
+ }
+ }
+ },
+ "@turf/boolean-contains": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/@turf/boolean-contains/-/boolean-contains-6.0.1.tgz",
+ "integrity": "sha512-usAexEdWu7dV43paowGSFEM0PljexnlOuj09HF/VDZwO3FKelwUovF2ymetYatuG7KcIYcexeNEkQ5qQnGExlw==",
+ "requires": {
+ "@turf/bbox": "6.x",
+ "@turf/boolean-point-in-polygon": "6.x",
+ "@turf/boolean-point-on-line": "6.x",
+ "@turf/helpers": "6.x",
+ "@turf/invariant": "6.x"
+ }
+ },
+ "@turf/boolean-point-in-polygon": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/@turf/boolean-point-in-polygon/-/boolean-point-in-polygon-6.0.1.tgz",
+ "integrity": "sha512-FKLOZ124vkJhjzNSDcqpwp2NvfnsbYoUOt5iAE7uskt4svix5hcjIEgX9sELFTJpbLGsD1mUbKdfns8tZxcMNg==",
+ "requires": {
+ "@turf/helpers": "6.x",
+ "@turf/invariant": "6.x"
+ }
+ },
+ "@turf/boolean-point-on-line": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/@turf/boolean-point-on-line/-/boolean-point-on-line-6.0.1.tgz",
+ "integrity": "sha512-Vl724Tzh4CF/13kgblOAQnMcHagromCP1EfyJ9G/8SxpSoTYeY2G6FmmcpbW51GqKxC7xgM9+Pck50dun7oYkg==",
+ "requires": {
+ "@turf/helpers": "6.x",
+ "@turf/invariant": "6.x"
+ }
+ },
+ "@turf/destination": {
+ "version": "5.1.5",
+ "resolved": "https://registry.npmjs.org/@turf/destination/-/destination-5.1.5.tgz",
+ "integrity": "sha1-7TU4G9zoO73cvQei4rzivd/7zCY=",
+ "requires": {
+ "@turf/helpers": "^5.1.5",
+ "@turf/invariant": "^5.1.5"
+ },
+ "dependencies": {
+ "@turf/helpers": {
+ "version": "5.1.5",
+ "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-5.1.5.tgz",
+ "integrity": "sha1-FTQFInq5M9AEpbuWQantmZ/L4M8="
+ },
+ "@turf/invariant": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@turf/invariant/-/invariant-5.2.0.tgz",
+ "integrity": "sha1-8BUP9ykLOFd7c9CIt5MsHuCqkKc=",
+ "requires": {
+ "@turf/helpers": "^5.1.5"
+ }
+ }
+ }
+ },
+ "@turf/distance": {
+ "version": "5.1.5",
+ "resolved": "https://registry.npmjs.org/@turf/distance/-/distance-5.1.5.tgz",
+ "integrity": "sha1-Oc8YIEu/h1h9cH5gmmARiQkVZAk=",
+ "requires": {
+ "@turf/helpers": "^5.1.5",
+ "@turf/invariant": "^5.1.5"
+ },
+ "dependencies": {
+ "@turf/helpers": {
+ "version": "5.1.5",
+ "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-5.1.5.tgz",
+ "integrity": "sha1-FTQFInq5M9AEpbuWQantmZ/L4M8="
+ },
+ "@turf/invariant": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@turf/invariant/-/invariant-5.2.0.tgz",
+ "integrity": "sha1-8BUP9ykLOFd7c9CIt5MsHuCqkKc=",
+ "requires": {
+ "@turf/helpers": "^5.1.5"
+ }
+ }
+ }
+ },
"@turf/helpers": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-6.1.4.tgz",
@@ -1184,6 +1286,87 @@
"@turf/meta": "6.x"
}
},
+ "@turf/line-split": {
+ "version": "5.1.5",
+ "resolved": "https://registry.npmjs.org/@turf/line-split/-/line-split-5.1.5.tgz",
+ "integrity": "sha1-Wy30w3YZty73JbUWPPmSbVVArLc=",
+ "requires": {
+ "@turf/bbox": "^5.1.5",
+ "@turf/helpers": "^5.1.5",
+ "@turf/invariant": "^5.1.5",
+ "@turf/line-intersect": "^5.1.5",
+ "@turf/line-segment": "^5.1.5",
+ "@turf/meta": "^5.1.5",
+ "@turf/nearest-point-on-line": "^5.1.5",
+ "@turf/square": "^5.1.5",
+ "@turf/truncate": "^5.1.5",
+ "geojson-rbush": "2.1.0"
+ },
+ "dependencies": {
+ "@turf/bbox": {
+ "version": "5.1.5",
+ "resolved": "https://registry.npmjs.org/@turf/bbox/-/bbox-5.1.5.tgz",
+ "integrity": "sha1-MFHfUUrUxQ9KT5uKLRX9i2hA7aM=",
+ "requires": {
+ "@turf/helpers": "^5.1.5",
+ "@turf/meta": "^5.1.5"
+ }
+ },
+ "@turf/helpers": {
+ "version": "5.1.5",
+ "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-5.1.5.tgz",
+ "integrity": "sha1-FTQFInq5M9AEpbuWQantmZ/L4M8="
+ },
+ "@turf/invariant": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@turf/invariant/-/invariant-5.2.0.tgz",
+ "integrity": "sha1-8BUP9ykLOFd7c9CIt5MsHuCqkKc=",
+ "requires": {
+ "@turf/helpers": "^5.1.5"
+ }
+ },
+ "@turf/line-intersect": {
+ "version": "5.1.5",
+ "resolved": "https://registry.npmjs.org/@turf/line-intersect/-/line-intersect-5.1.5.tgz",
+ "integrity": "sha1-DikHGuQDKV5JFyO8SfXPrI0R3fM=",
+ "requires": {
+ "@turf/helpers": "^5.1.5",
+ "@turf/invariant": "^5.1.5",
+ "@turf/line-segment": "^5.1.5",
+ "@turf/meta": "^5.1.5",
+ "geojson-rbush": "2.1.0"
+ }
+ },
+ "@turf/line-segment": {
+ "version": "5.1.5",
+ "resolved": "https://registry.npmjs.org/@turf/line-segment/-/line-segment-5.1.5.tgz",
+ "integrity": "sha1-Mgeq7lRqskw9jcPMY/kcdwuAE+U=",
+ "requires": {
+ "@turf/helpers": "^5.1.5",
+ "@turf/invariant": "^5.1.5",
+ "@turf/meta": "^5.1.5"
+ }
+ },
+ "@turf/meta": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@turf/meta/-/meta-5.2.0.tgz",
+ "integrity": "sha1-OxrUhe4MOwsXdRMqMsOE1T5LpT0=",
+ "requires": {
+ "@turf/helpers": "^5.1.5"
+ }
+ },
+ "geojson-rbush": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/geojson-rbush/-/geojson-rbush-2.1.0.tgz",
+ "integrity": "sha1-O9c745H8ELCuaT2bis6iquC4Oo0=",
+ "requires": {
+ "@turf/helpers": "*",
+ "@turf/meta": "*",
+ "rbush": "*"
+ }
+ }
+ }
+ },
"@turf/meta": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@turf/meta/-/meta-6.0.2.tgz",
@@ -1192,6 +1375,115 @@
"@turf/helpers": "6.x"
}
},
+ "@turf/nearest-point-on-line": {
+ "version": "5.1.5",
+ "resolved": "https://registry.npmjs.org/@turf/nearest-point-on-line/-/nearest-point-on-line-5.1.5.tgz",
+ "integrity": "sha1-VgauKX8VlHUkvqUaKp71HsG/nDY=",
+ "requires": {
+ "@turf/bearing": "^5.1.5",
+ "@turf/destination": "^5.1.5",
+ "@turf/distance": "^5.1.5",
+ "@turf/helpers": "^5.1.5",
+ "@turf/invariant": "^5.1.5",
+ "@turf/line-intersect": "^5.1.5",
+ "@turf/meta": "^5.1.5"
+ },
+ "dependencies": {
+ "@turf/helpers": {
+ "version": "5.1.5",
+ "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-5.1.5.tgz",
+ "integrity": "sha1-FTQFInq5M9AEpbuWQantmZ/L4M8="
+ },
+ "@turf/invariant": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@turf/invariant/-/invariant-5.2.0.tgz",
+ "integrity": "sha1-8BUP9ykLOFd7c9CIt5MsHuCqkKc=",
+ "requires": {
+ "@turf/helpers": "^5.1.5"
+ }
+ },
+ "@turf/line-intersect": {
+ "version": "5.1.5",
+ "resolved": "https://registry.npmjs.org/@turf/line-intersect/-/line-intersect-5.1.5.tgz",
+ "integrity": "sha1-DikHGuQDKV5JFyO8SfXPrI0R3fM=",
+ "requires": {
+ "@turf/helpers": "^5.1.5",
+ "@turf/invariant": "^5.1.5",
+ "@turf/line-segment": "^5.1.5",
+ "@turf/meta": "^5.1.5",
+ "geojson-rbush": "2.1.0"
+ }
+ },
+ "@turf/line-segment": {
+ "version": "5.1.5",
+ "resolved": "https://registry.npmjs.org/@turf/line-segment/-/line-segment-5.1.5.tgz",
+ "integrity": "sha1-Mgeq7lRqskw9jcPMY/kcdwuAE+U=",
+ "requires": {
+ "@turf/helpers": "^5.1.5",
+ "@turf/invariant": "^5.1.5",
+ "@turf/meta": "^5.1.5"
+ }
+ },
+ "@turf/meta": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@turf/meta/-/meta-5.2.0.tgz",
+ "integrity": "sha1-OxrUhe4MOwsXdRMqMsOE1T5LpT0=",
+ "requires": {
+ "@turf/helpers": "^5.1.5"
+ }
+ },
+ "geojson-rbush": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/geojson-rbush/-/geojson-rbush-2.1.0.tgz",
+ "integrity": "sha1-O9c745H8ELCuaT2bis6iquC4Oo0=",
+ "requires": {
+ "@turf/helpers": "*",
+ "@turf/meta": "*",
+ "rbush": "*"
+ }
+ }
+ }
+ },
+ "@turf/square": {
+ "version": "5.1.5",
+ "resolved": "https://registry.npmjs.org/@turf/square/-/square-5.1.5.tgz",
+ "integrity": "sha1-qnsh5gM8ySUsOlvW89iNq9b+0YA=",
+ "requires": {
+ "@turf/distance": "^5.1.5",
+ "@turf/helpers": "^5.1.5"
+ },
+ "dependencies": {
+ "@turf/helpers": {
+ "version": "5.1.5",
+ "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-5.1.5.tgz",
+ "integrity": "sha1-FTQFInq5M9AEpbuWQantmZ/L4M8="
+ }
+ }
+ },
+ "@turf/truncate": {
+ "version": "5.1.5",
+ "resolved": "https://registry.npmjs.org/@turf/truncate/-/truncate-5.1.5.tgz",
+ "integrity": "sha1-nu37Oxi6gfLJjT6tCUMcyhiErYk=",
+ "requires": {
+ "@turf/helpers": "^5.1.5",
+ "@turf/meta": "^5.1.5"
+ },
+ "dependencies": {
+ "@turf/helpers": {
+ "version": "5.1.5",
+ "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-5.1.5.tgz",
+ "integrity": "sha1-FTQFInq5M9AEpbuWQantmZ/L4M8="
+ },
+ "@turf/meta": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@turf/meta/-/meta-5.2.0.tgz",
+ "integrity": "sha1-OxrUhe4MOwsXdRMqMsOE1T5LpT0=",
+ "requires": {
+ "@turf/helpers": "^5.1.5"
+ }
+ }
+ }
+ },
"@types/json-schema": {
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz",
@@ -3235,6 +3527,12 @@
}
}
},
+ "cypress-wait-until": {
+ "version": "1.7.1",
+ "resolved": "https://registry.npmjs.org/cypress-wait-until/-/cypress-wait-until-1.7.1.tgz",
+ "integrity": "sha512-8DL5IsBTbAxBjfYgCzdbohPq/bY+IKc63fxtso1C8RWhLnQkZbVESyaclNr76jyxfId6uyzX8+Xnt0ZwaXNtkA==",
+ "dev": true
+ },
"dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
diff --git a/package.json b/package.json
index ccd2bbff..8d96aac1 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@geoman-io/leaflet-geoman-free",
- "version": "2.7.0",
+ "version": "2.8.0",
"description": "A Leaflet Plugin For Editing Geometry Layers in Leaflet 1.0",
"keywords": [
"leaflet",
@@ -23,7 +23,9 @@
"@turf/kinks": "6.x",
"@turf/line-intersect": "^6.0.2",
"polygon-clipping": "^0.15.1",
- "lodash": "^4.17.15"
+ "lodash": "^4.17.15",
+ "@turf/boolean-contains": "^6.0.1",
+ "@turf/line-split": "^5.1.5"
},
"devDependencies": {
"@babel/core": "^7.9.6",
@@ -32,6 +34,7 @@
"copy-webpack-plugin": "^6.2.1",
"css-loader": "^3.5.3",
"cypress": "^3.8.3",
+ "cypress-wait-until": "^1.7.1",
"eslint": "^4.19.1",
"eslint-config-airbnb-base": "^12.1.0",
"eslint-config-prettier": "^3.6.0",
diff --git a/src/assets/translations/da.json b/src/assets/translations/da.json
new file mode 100644
index 00000000..b4a469dd
--- /dev/null
+++ b/src/assets/translations/da.json
@@ -0,0 +1,32 @@
+{
+ "tooltips": {
+ "placeMarker": "Tryk for at placere en markør",
+ "firstVertex": "Tryk for at placere det første punkt",
+ "continueLine": "Tryk for at fortsætte linjen",
+ "finishLine": "Tryk på et eksisterende punkt for at afslutte",
+ "finishPoly": "Tryk på det første punkt for at afslutte",
+ "finishRect": "Tryk for at afslutte",
+ "startCircle": "Tryk for at placere cirklens center",
+ "finishCircle": "Tryk for at afslutte cirklen",
+ "placeCircleMarker": "Tryk for at placere en cirkelmarkør"
+ },
+ "actions": {
+ "finish": "Afslut",
+ "cancel": "Afbryd",
+ "removeLastVertex": "Fjern sidste punkt"
+ },
+ "buttonTitles": {
+ "drawMarkerButton": "Placer markør",
+ "drawPolyButton": "Tegn polygon",
+ "drawLineButton": "Tegn linje",
+ "drawCircleButton": "Tegn cirkel",
+ "drawRectButton": "Tegn firkant",
+ "editButton": "Rediger",
+ "dragButton": "Træk",
+ "cutButton": "Klip",
+ "deleteButton": "Fjern",
+ "drawCircleMarkerButton": "Tegn cirkelmarkør",
+ "snappingButton": "Fastgør trukket markør til andre elementer",
+ "pinningButton": "Sammenlæg delte elementer"
+ }
+}
\ No newline at end of file
diff --git a/src/assets/translations/de.json b/src/assets/translations/de.json
index f84b179d..5a99ec32 100644
--- a/src/assets/translations/de.json
+++ b/src/assets/translations/de.json
@@ -25,6 +25,8 @@
"dragButton": "Layer bewegen",
"cutButton": "Layer schneiden",
"deleteButton": "Layer löschen",
- "drawCircleMarkerButton": "Kreismarker zeichnen"
+ "drawCircleMarkerButton": "Kreismarker zeichnen",
+ "snappingButton": "Bewegter Layer an andere Layer oder Vertexe einhacken",
+ "pinningButton": "Vertexe an der gleichen Position verknüpfen"
}
}
diff --git a/src/assets/translations/en.json b/src/assets/translations/en.json
index 57b37843..b5b052fa 100644
--- a/src/assets/translations/en.json
+++ b/src/assets/translations/en.json
@@ -29,4 +29,4 @@
"snappingButton": "Snap dragged marker to other layers and vertices",
"pinningButton": "Pin shared vertices together"
}
-}
\ No newline at end of file
+}
diff --git a/src/assets/translations/es.json b/src/assets/translations/es.json
index e975bfce..e6d04e4b 100644
--- a/src/assets/translations/es.json
+++ b/src/assets/translations/es.json
@@ -25,6 +25,8 @@
"dragButton": "Arrastrar Capas",
"cutButton": "Cortar Capas",
"deleteButton": "Remover Capas",
- "drawCircleMarkerButton": "Dibujar Marcador de Circulo"
+ "drawCircleMarkerButton": "Dibujar Marcador de Circulo",
+ "snappingButton": "El marcador de Snap arrastrado a otras capas y vértices",
+ "pinningButton": "Fijar juntos los vértices compartidos"
}
}
diff --git a/src/assets/translations/fr.json b/src/assets/translations/fr.json
index 942c5a8b..ff647492 100644
--- a/src/assets/translations/fr.json
+++ b/src/assets/translations/fr.json
@@ -7,7 +7,8 @@
"finishPoly": "Cliquez sur le premier marqueur pour terminer",
"finishRect": "Cliquez pour terminer",
"startCircle": "Cliquez pour placer le centre du cercle",
- "finishCircle": "Cliquez pour finir le cercle"
+ "finishCircle": "Cliquez pour finir le cercle",
+ "placeCircleMarker": "Cliquez pour placer le marqueur circulaire"
},
"actions": {
"finish": "Terminer",
@@ -23,6 +24,9 @@
"editButton": "Éditer des calques",
"dragButton": "Déplacer des calques",
"cutButton": "Couper des calques",
- "deleteButton": "Supprimer des calques"
+ "deleteButton": "Supprimer des calques",
+ "drawCircleMarkerButton": "Dessiner un marqueur circulaire",
+ "snappingButton": "Glisser le marqueur vers d'autres couches et sommets",
+ "pinningButton": "Épingler ensemble les sommets partagés"
}
}
diff --git a/src/assets/translations/id.json b/src/assets/translations/id.json
index ae3b369c..129f05c4 100644
--- a/src/assets/translations/id.json
+++ b/src/assets/translations/id.json
@@ -7,7 +7,8 @@
"finishPoly": "Klik marker pertama untuk mengakhiri",
"finishRect": "Klik untuk mengakhiri",
"startCircle": "Klik untuk menempatkan titik pusat lingkaran",
- "finishCircle": "Klik untuk mengakhiri lingkaran"
+ "finishCircle": "Klik untuk mengakhiri lingkaran",
+ "placeCircleMarker": "Klik untuk menempatkan penanda lingkarann"
},
"actions": {
"finish": "Selesai",
@@ -23,6 +24,9 @@
"editButton": "Edit Layer",
"dragButton": "Geser Layer",
"cutButton": "Potong Layer",
- "deleteButton": "Hilangkan Layer"
+ "deleteButton": "Hilangkan Layer",
+ "drawCircleMarkerButton": "Digitasi Penanda Lingkaran",
+ "snappingButton": "Jepretkan penanda yang ditarik ke lapisan dan simpul lain",
+ "pinningButton": "Sematkan simpul bersama bersama"
}
}
diff --git a/src/assets/translations/index.js b/src/assets/translations/index.js
index 96538bea..bfa54d76 100644
--- a/src/assets/translations/index.js
+++ b/src/assets/translations/index.js
@@ -9,11 +9,14 @@ import nl from './nl.json';
import fr from './fr.json';
import zh from './zh.json';
// eslint-disable-next-line camelcase
+import zh_tw from './zh_tw.json';
+// eslint-disable-next-line camelcase
import pt_br from './pt_br.json';
import pl from './pl.json';
import sv from './sv.json';
import el from './el.json';
import hu from './hu.json';
+import da from './da.json';
export default {
en,
@@ -27,8 +30,10 @@ export default {
fr,
pt_br,
zh,
+ zh_tw,
pl,
sv,
el,
- hu
+ hu,
+ da
};
diff --git a/src/assets/translations/it.json b/src/assets/translations/it.json
index 850cad9f..21f7e08e 100644
--- a/src/assets/translations/it.json
+++ b/src/assets/translations/it.json
@@ -25,6 +25,8 @@
"dragButton": "Sposta Livelli",
"cutButton": "Ritaglia Livelli",
"deleteButton": "Elimina Livelli",
- "drawCircleMarkerButton": "Disegna Marker del Cherchio"
+ "drawCircleMarkerButton": "Disegna Marker del Cerchio",
+ "snappingButton": "Snap ha trascinato il pennarello su altri strati e vertici",
+ "pinningButton": "Pin condiviso vertici insieme"
}
}
diff --git a/src/assets/translations/nl.json b/src/assets/translations/nl.json
index 645f0810..92acb090 100644
--- a/src/assets/translations/nl.json
+++ b/src/assets/translations/nl.json
@@ -25,6 +25,8 @@
"dragButton": "Verplaats",
"cutButton": "Knip",
"deleteButton": "Verwijder",
- "drawCircleMarkerButton": "Plaats Marker"
+ "drawCircleMarkerButton": "Plaats Marker",
+ "snappingButton": "Snap gesleepte marker naar andere lagen en hoekpunten",
+ "pinningButton": "Speld gedeelde hoekpunten samen"
}
}
diff --git a/src/assets/translations/pl.json b/src/assets/translations/pl.json
index c043a036..1c4c2938 100644
--- a/src/assets/translations/pl.json
+++ b/src/assets/translations/pl.json
@@ -25,6 +25,8 @@
"dragButton": "Przesuń",
"cutButton": "Wytnij",
"deleteButton": "Usuń",
- "drawCircleMarkerButton": "Narysuj okrągły znacznik"
+ "drawCircleMarkerButton": "Narysuj okrągły znacznik",
+ "snappingButton": "Snap przeciągnięty marker na inne warstwy i wierzchołki",
+ "pinningButton": "Sworzeń wspólne wierzchołki razem"
}
}
diff --git a/src/assets/translations/pt_br.json b/src/assets/translations/pt_br.json
index b45753df..5a12666f 100644
--- a/src/assets/translations/pt_br.json
+++ b/src/assets/translations/pt_br.json
@@ -7,7 +7,8 @@
"finishPoly": "Clique no primeiro ponto para fechar o polígono",
"finishRect": "Clique para finalizar",
"startCircle": "Clique para posicionar o centro do círculo",
- "finishCircle": "Clique para fechar o círculo"
+ "finishCircle": "Clique para fechar o círculo",
+ "placeCircleMarker": "Clique para posicionar o marcador circular"
},
"actions": {
"finish": "Finalizar",
@@ -23,7 +24,9 @@
"editButton": "Editar camada(s)",
"dragButton": "Mover camada(s)",
"cutButton": "Recortar camada(s)",
- "deleteButton": "Remover camada(s)"
+ "deleteButton": "Remover camada(s)",
+ "drawCircleMarkerButton": "Marcador de círculos de desenho",
+ "snappingButton": "Marcador arrastado para outras camadas e vértices",
+ "pinningButton": "Vértices compartilhados de pinos juntos"
}
}
-
\ No newline at end of file
diff --git a/src/assets/translations/ro.json b/src/assets/translations/ro.json
index a80a990d..01c3d5d6 100644
--- a/src/assets/translations/ro.json
+++ b/src/assets/translations/ro.json
@@ -25,6 +25,8 @@
"dragButton": "Mută straturile",
"cutButton": "Taie straturile",
"deleteButton": "Șterge straturile",
- "placeCircleMarker": "Adaugă o bulină"
+ "drawCircleMarkerButton": "Desenează marcatorul cercului",
+ "snappingButton": "Fixați marcatorul glisat pe alte straturi și vârfuri",
+ "pinningButton": "Fixați vârfurile partajate împreună"
}
}
diff --git a/src/assets/translations/sv.json b/src/assets/translations/sv.json
index 78ae7f57..f5c96404 100644
--- a/src/assets/translations/sv.json
+++ b/src/assets/translations/sv.json
@@ -25,6 +25,8 @@
"dragButton": "Dra Lager",
"cutButton": "Klipp i Lager",
"deleteButton": "Ta bort Lager",
- "drawCircleMarkerButton": "Rita Cirkelmarkör"
+ "drawCircleMarkerButton": "Rita Cirkelmarkör",
+ "snappingButton": "Snäpp dra markören till andra lager och hörn",
+ "pinningButton": "Fäst delade hörn tillsammans"
}
}
diff --git a/src/assets/translations/zh.json b/src/assets/translations/zh.json
index 76f97df2..e835fac0 100644
--- a/src/assets/translations/zh.json
+++ b/src/assets/translations/zh.json
@@ -7,7 +7,8 @@
"finishPoly": "单击第一个标记以完成",
"finishRect": "单击完成",
"startCircle": "单击放置圆心",
- "finishCircle": "单击完成圆形"
+ "finishCircle": "单击完成圆形",
+ "placeCircleMarker": "点击放置圆形标记"
},
"actions": {
"finish": "完成",
@@ -23,6 +24,9 @@
"editButton": "编辑图层",
"dragButton": "拖拽图层",
"cutButton": "剪切图层",
- "deleteButton": "删除图层"
+ "deleteButton": "删除图层",
+ "drawCircleMarkerButton": "画圆圈标记",
+ "snappingButton": "将拖动的标记捕捉到其他图层和顶点",
+ "pinningButton": "将共享顶点固定在一起"
}
}
diff --git a/src/assets/translations/zh_tw.json b/src/assets/translations/zh_tw.json
new file mode 100644
index 00000000..9517c92d
--- /dev/null
+++ b/src/assets/translations/zh_tw.json
@@ -0,0 +1,32 @@
+{
+ "tooltips": {
+ "placeMarker": "單擊放置標記",
+ "firstVertex": "單擊放置第一個頂點",
+ "continueLine": "單擊繼續繪製",
+ "finishLine": "單擊任何存在的標記以完成",
+ "finishPoly": "單擊第一個標記以完成",
+ "finishRect": "單擊完成",
+ "startCircle": "單擊放置圓心",
+ "finishCircle": "單擊完成圓形",
+ "placeCircleMarker": "點擊放置圓形標記"
+ },
+ "actions": {
+ "finish": "完成",
+ "cancel": "取消",
+ "removeLastVertex": "移除最後一個頂點"
+ },
+ "buttonTitles": {
+ "drawMarkerButton": "放置標記",
+ "drawPolyButton": "繪製多邊形",
+ "drawLineButton": "繪製線段",
+ "drawCircleButton": "繪製圓形",
+ "drawRectButton": "繪製方形",
+ "editButton": "編輯圖形",
+ "dragButton": "移動圖形",
+ "cutButton": "裁切圖形",
+ "deleteButton": "刪除圖形",
+ "drawCircleMarkerButton": "畫圓圈標記",
+ "snappingButton": "將拖動的標記對齊到其他圖層和頂點",
+ "pinningButton": "將共享頂點固定在一起"
+ }
+}
diff --git a/src/css/controls.css b/src/css/controls.css
index a4f2d5a8..af0ef3da 100644
--- a/src/css/controls.css
+++ b/src/css/controls.css
@@ -100,6 +100,7 @@
left: 100%;
display: none;
white-space: nowrap;
+ direction: ltr;
}
.leaflet-right
@@ -144,3 +145,10 @@
z-index: 801;
}
+.leaflet-buttons-control-button.pm-disabled{
+ background-color: #f4f4f4 !important;
+}
+
+.control-icon.pm-disabled{
+ filter: opacity(0.6);
+}
diff --git a/src/js/Draw/L.PM.Draw.Circle.js b/src/js/Draw/L.PM.Draw.Circle.js
index c847b073..61f13f79 100644
--- a/src/js/Draw/L.PM.Draw.Circle.js
+++ b/src/js/Draw/L.PM.Draw.Circle.js
@@ -1,6 +1,7 @@
import Draw from './L.PM.Draw';
-import { getTranslation } from '../helpers';
+import {destinationOnLine, getTranslation} from '../helpers';
+import Utils from "../L.PM.Utils";
Draw.Circle = Draw.extend({
initialize(map) {
@@ -55,7 +56,6 @@ Draw.Circle = Draw.extend({
permanent: true,
offset: L.point(0, 10),
direction: 'bottom',
-
opacity: 0.8,
})
.openTooltip();
@@ -75,12 +75,6 @@ Draw.Circle = Draw.extend({
// sync hint marker with mouse cursor
this._map.on('mousemove', this._syncHintMarker, this);
- // fire drawstart event
- this._map.fire('pm:drawstart', {
- shape: this._shape,
- workingLayer: this._layer,
- });
- this._setGlobalDrawMode();
// toggle the draw button of the Toolbar in case drawing mode got enabled without the button
this._map.pm.Toolbar.toggleButton(this.toolbarButtonName, true);
@@ -88,6 +82,13 @@ Draw.Circle = Draw.extend({
// an array used in the snapping mixin.
// TODO: think about moving this somewhere else?
this._otherSnapLayers = [];
+
+ // fire drawstart event
+ Utils._fireEvent(this._map,'pm:drawstart', {
+ shape: this._shape,
+ workingLayer: this._layer,
+ });
+ this._setGlobalDrawMode();
},
disable() {
// disable drawing mode
@@ -110,9 +111,6 @@ Draw.Circle = Draw.extend({
// remove helping layers
this._map.removeLayer(this._layerGroup);
- // fire drawend event
- this._map.fire('pm:drawend', { shape: this._shape });
- this._setGlobalDrawMode();
// toggle the draw button of the Toolbar in case drawing mode got disabled without the button
this._map.pm.Toolbar.toggleButton(this.toolbarButtonName, false);
@@ -121,6 +119,10 @@ Draw.Circle = Draw.extend({
if (this.options.snappable) {
this._cleanupSnapping();
}
+
+ // fire drawend event
+ Utils._fireEvent(this._map,'pm:drawend', { shape: this._shape });
+ this._setGlobalDrawMode();
},
enabled() {
return this._enabled;
@@ -134,21 +136,35 @@ Draw.Circle = Draw.extend({
},
_syncHintLine() {
const latlng = this._centerMarker.getLatLng();
-
+ const secondLatLng = this._getNewDestinationOfHintMarker();
// set coords for hintline from marker to last vertex of drawin polyline
- this._hintline.setLatLngs([latlng, this._hintMarker.getLatLng()]);
+ this._hintline.setLatLngs([latlng, secondLatLng]);
},
_syncCircleRadius() {
const A = this._centerMarker.getLatLng();
const B = this._hintMarker.getLatLng();
- const distance = A.distanceTo(B);
+ let distance;
- this._layer.setRadius(distance);
+ if (this._map.options.crs === L.CRS.Simple) {
+ distance = this._map.distance(A, B);
+ } else {
+ distance = A.distanceTo(B);
+ }
+
+ if(this.options.minRadiusCircle && distance < this.options.minRadiusCircle) {
+ this._layer.setRadius(this.options.minRadiusCircle);
+ }else if(this.options.maxRadiusCircle && distance > this.options.maxRadiusCircle) {
+ this._layer.setRadius(this.options.maxRadiusCircle);
+ }else{
+ this._layer.setRadius(distance);
+ }
},
_syncHintMarker(e) {
// move the cursor marker
this._hintMarker.setLatLng(e.latlng);
+ // calculate the new latlng of marker if radius is out of min/max
+ this._hintMarker.setLatLng(this._getNewDestinationOfHintMarker());
// if snapping is enabled, do it
if (this.options.snappable) {
@@ -156,6 +172,8 @@ Draw.Circle = Draw.extend({
fakeDragEvent.target = this._hintMarker;
this._handleSnapping(fakeDragEvent);
}
+
+ this._handleHintMarkerSnapping();
},
_placeCenterMarker(e) {
// assign the coordinate of the click to the hintMarker, that's necessary for
@@ -188,7 +206,7 @@ Draw.Circle = Draw.extend({
getTranslation('tooltips.finishCircle')
);
- this._layer.fire('pm:centerplaced', {
+ Utils._fireEvent(this._layer,'pm:centerplaced', {
workingLayer: this._layer,
latlng,
shape: this._shape
@@ -205,37 +223,68 @@ Draw.Circle = Draw.extend({
// calc the radius
const center = this._centerMarker.getLatLng();
const latlng = this._hintMarker.getLatLng();
- const radius = center.distanceTo(latlng);
+
+ let radius;
+
+ if (this._map.options.crs === L.CRS.Simple) {
+ radius = this._map.distance(center, latlng);
+ } else {
+ radius = center.distanceTo(latlng);
+ }
+
+ if(this.options.minRadiusCircle && radius < this.options.minRadiusCircle){
+ radius = this.options.minRadiusCircle;
+ }else if(this.options.maxRadiusCircle && radius > this.options.maxRadiusCircle){
+ radius = this.options.maxRadiusCircle;
+ }
+
const options = Object.assign({}, this.options.pathOptions, { radius });
// create the final circle layer
- const circleLayer = L.circle(center, options).addTo(this._map);
- this._setShapeForFinishLayer(circleLayer);
- this._addDrawnLayerProp(circleLayer);
-
- // create polygon around the circle border
- circleLayer.pm._updateHiddenPolyCircle();
+ const circleLayer = L.circle(center, options).addTo(this._map.pm._getContainingLayer());
+ this._finishLayer(circleLayer);
- // disable drawing
- this.disable();
+ if(circleLayer.pm) {
+ // create polygon around the circle border
+ circleLayer.pm._updateHiddenPolyCircle();
+ }
// fire the pm:create event and pass shape and layer
- this._map.fire('pm:create', {
+ Utils._fireEvent(this._map,'pm:create', {
shape: this._shape,
layer: circleLayer,
});
- },
- _createMarker(latlng) {
- // create the new marker
- const marker = new L.Marker(latlng, {
- draggable: false,
- icon: L.divIcon({ className: 'marker-icon' }),
- });
- marker._pmTempLayer = true;
- // add it to the map
- this._layerGroup.addLayer(marker);
+ // disable drawing
+ this.disable();
+ if(this.options.continueDrawing){
+ this.enable();
+ }
+ },
+ _getNewDestinationOfHintMarker(){
+ const latlng = this._centerMarker.getLatLng();
+ let secondLatLng = this._hintMarker.getLatLng();
+ const distance = latlng.distanceTo(secondLatLng);
- return marker;
+ if(this.options.minRadiusCircle && distance < this.options.minRadiusCircle) {
+ secondLatLng = destinationOnLine(this._map,latlng,secondLatLng,this.options.minRadiusCircle);
+ }else if(this.options.maxRadiusCircle && distance > this.options.maxRadiusCircle) {
+ secondLatLng = destinationOnLine(this._map,latlng,secondLatLng,this.options.maxRadiusCircle);
+ }
+ return secondLatLng;
},
+ _handleHintMarkerSnapping(){
+ if(this._hintMarker._snapped) {
+ const latlng = this._centerMarker.getLatLng();
+ const secondLatLng = this._hintMarker.getLatLng();
+ const distance = latlng.distanceTo(secondLatLng);
+ if(this.options.minRadiusCircle && distance < this.options.minRadiusCircle) {
+ this._hintMarker.setLatLng(this._hintMarker._orgLatLng);
+ } else if(this.options.maxRadiusCircle && distance > this.options.maxRadiusCircle) {
+ this._hintMarker.setLatLng(this._hintMarker._orgLatLng);
+ }
+ }
+ // calculate the new latlng of marker if the snapped latlng radius is out of min/max
+ this._hintMarker.setLatLng(this._getNewDestinationOfHintMarker());
+ }
});
diff --git a/src/js/Draw/L.PM.Draw.CircleMarker.js b/src/js/Draw/L.PM.Draw.CircleMarker.js
index a2c4d2b5..98f3fc3f 100644
--- a/src/js/Draw/L.PM.Draw.CircleMarker.js
+++ b/src/js/Draw/L.PM.Draw.CircleMarker.js
@@ -1,12 +1,15 @@
import Draw from './L.PM.Draw';
-import { getTranslation } from '../helpers';
+import {destinationOnLine, getTranslation } from '../helpers';
+import Utils from "../L.PM.Utils";
Draw.CircleMarker = Draw.Marker.extend({
initialize(map) {
this._map = map;
this._shape = 'CircleMarker';
this.toolbarButtonName = 'drawCircleMarker';
+ // with _layerIsDragging we check if a circlemarker is currently dragged and disable marker creation
+ this._layerIsDragging = false;
},
enable(options) {
// TODO: Think about if these options could be passed globally for all
@@ -99,18 +102,10 @@ Draw.CircleMarker = Draw.Marker.extend({
}
}
-
// sync hint marker with mouse cursor
this._map.on('mousemove', this._syncHintMarker, this);
- // fire drawstart event
- this._map.fire('pm:drawstart', {
- shape: this._shape,
- workingLayer: this._layer,
- });
- this._setGlobalDrawMode();
-
- if (!this.options.editable) {
+ if (!this.options.editable && this.options.markerEditable) {
// enable edit mode for existing markers
this._map.eachLayer(layer => {
if (this.isRelevantMarker(layer)) {
@@ -118,12 +113,24 @@ Draw.CircleMarker = Draw.Marker.extend({
}
});
}
+
+ this._layer.bringToBack();
+
+ // fire drawstart event
+ Utils._fireEvent(this._map,'pm:drawstart', {
+ shape: this._shape,
+ workingLayer: this._layer,
+ });
+ this._setGlobalDrawMode();
},
disable() {
// cancel, if drawing mode isn't even enabled
if (!this._enabled) {
return;
}
+ // change enabled state
+ this._enabled = false;
+
// disable when drawing like a Circle
if (this.options.editable) {
// reset cursor
@@ -153,9 +160,6 @@ Draw.CircleMarker = Draw.Marker.extend({
// remove event listener to sync hint marker
this._map.off('mousemove', this._syncHintMarker, this);
- // fire drawend event
- this._map.fire('pm:drawend', { shape: this._shape });
-
// toggle the draw button of the Toolbar in case drawing mode got disabled without the button
this._map.pm.Toolbar.toggleButton(this.toolbarButtonName, false);
@@ -164,8 +168,9 @@ Draw.CircleMarker = Draw.Marker.extend({
this._cleanupSnapping();
}
- // change enabled state
- this._enabled = false;
+ // fire drawend event
+ Utils._fireEvent(this._map,'pm:drawend', { shape: this._shape });
+ this._setGlobalDrawMode();
},
_placeCenterMarker(e) {
// assign the coordinate of the click to the hintMarker, that's necessary for
@@ -198,7 +203,7 @@ Draw.CircleMarker = Draw.Marker.extend({
getTranslation('tooltips.finishCircle')
);
- this._layer.fire('pm:centerplaced', {
+ Utils._fireEvent(this._layer,'pm:centerplaced', {
shape: this._shape,
workingLayer: this._layer,
latlng,
@@ -207,9 +212,9 @@ Draw.CircleMarker = Draw.Marker.extend({
},
_syncHintLine() {
const latlng = this._centerMarker.getLatLng();
-
+ const secondLatLng = this._getNewDestinationOfHintMarker();
// set coords for hintline from marker to last vertex of drawin polyline
- this._hintline.setLatLngs([latlng, this._hintMarker.getLatLng()]);
+ this._hintline.setLatLngs([latlng, secondLatLng]);
},
_syncCircleRadius() {
const A = this._centerMarker.getLatLng();
@@ -217,11 +222,19 @@ Draw.CircleMarker = Draw.Marker.extend({
const distance = this._map.project(A).distanceTo(this._map.project(B));
- this._layer.setRadius(distance);
+ if(this.options.minRadiusCircleMarker && distance < this.options.minRadiusCircleMarker) {
+ this._layer.setRadius(this.options.minRadiusCircleMarker);
+ }else if(this.options.maxRadiusCircleMarker && distance > this.options.maxRadiusCircleMarker) {
+ this._layer.setRadius(this.options.maxRadiusCircleMarker);
+ }else{
+ this._layer.setRadius(distance);
+ }
},
_syncHintMarker(e) {
// move the cursor marker
this._hintMarker.setLatLng(e.latlng);
+ // calculate the new latlng of marker if radius is out of min/max
+ this._hintMarker.setLatLng(this._getNewDestinationOfHintMarker());
// if snapping is enabled, do it
if (this.options.snappable) {
@@ -229,12 +242,15 @@ Draw.CircleMarker = Draw.Marker.extend({
fakeDragEvent.target = this._hintMarker;
this._handleSnapping(fakeDragEvent);
}
+
+ this._handleHintMarkerSnapping();
},
isRelevantMarker(layer) {
return layer instanceof L.CircleMarker && !(layer instanceof L.Circle) && layer.pm && !layer._pmTempLayer;
},
_createMarker(e) {
- if (!e.latlng) {
+ // with _layerIsDragging we check if a circlemarker is currently dragged
+ if (!e.latlng || this._layerIsDragging) {
return;
}
@@ -249,22 +265,27 @@ Draw.CircleMarker = Draw.Marker.extend({
// create marker
const marker = L.circleMarker(latlng, this.options.pathOptions);
- this._setShapeForFinishLayer(marker);
- this._addDrawnLayerProp(marker);
+ this._finishLayer(marker);
// add marker to the map
- marker.addTo(this._map);
+ marker.addTo(this._map.pm._getContainingLayer());
- // enable editing for the marker
- marker.pm.enable();
+ if(marker.pm && this.options.markerEditable) {
+ // enable editing for the marker
+ marker.pm.enable();
+ }
// fire the pm:create event and pass shape and marker
- this._map.fire('pm:create', {
+ Utils._fireEvent(this._map,'pm:create', {
shape: this._shape,
marker, // DEPRECATED
layer: marker,
});
this._cleanupSnapping();
+
+ if(!this.options.continueDrawing){
+ this.disable();
+ }
},
_finishShape(e) {
// assign the coordinate of the click to the hintMarker, that's necessary for
@@ -276,23 +297,72 @@ Draw.CircleMarker = Draw.Marker.extend({
// calc the radius
const center = this._centerMarker.getLatLng();
const latlng = this._hintMarker.getLatLng();
- const radius = this._map.project(center).distanceTo(this._map.project(latlng));
+ let radius = this._map.project(center).distanceTo(this._map.project(latlng));
+
+
+ if(this.options.editable) {
+ if (this.options.minRadiusCircleMarker && radius < this.options.minRadiusCircleMarker) {
+ radius = this.options.minRadiusCircleMarker;
+ } else if (this.options.maxRadiusCircleMarker && radius > this.options.maxRadiusCircleMarker) {
+ radius = this.options.maxRadiusCircleMarker;
+ }
+ }
+
const options = Object.assign({}, this.options.pathOptions, { radius });
// create the final circle layer
- const circleLayer = L.circleMarker(center, options).addTo(this._map);
- this._setShapeForFinishLayer(circleLayer);
- this._addDrawnLayerProp(circleLayer);
- // create polygon around the circle border
- circleLayer.pm._updateHiddenPolyCircle();
-
- // disable drawing
- this.disable();
+ const circleLayer = L.circleMarker(center, options).addTo(this._map.pm._getContainingLayer());
+ this._finishLayer(circleLayer);
+ if(circleLayer.pm) {
+ // create polygon around the circle border
+ circleLayer.pm._updateHiddenPolyCircle();
+ }
// fire the pm:create event and pass shape and layer
- this._map.fire('pm:create', {
+ Utils._fireEvent(this._map,'pm:create', {
shape: this._shape,
layer: circleLayer,
});
+
+ // disable drawing
+ this.disable();
+ if(this.options.continueDrawing){
+ this.enable();
+ }
+ },
+ _getNewDestinationOfHintMarker(){
+ let secondLatLng = this._hintMarker.getLatLng();
+ if(this.options.editable) {
+ const latlng = this._centerMarker.getLatLng();
+ const distance = this._map.project(latlng).distanceTo(this._map.project(secondLatLng));
+ if (this.options.minRadiusCircleMarker && distance < this.options.minRadiusCircleMarker) {
+ secondLatLng = destinationOnLine(this._map, latlng, secondLatLng, this._pxRadiusToMeter(this.options.minRadiusCircleMarker));
+ } else if (this.options.maxRadiusCircleMarker && distance > this.options.maxRadiusCircleMarker) {
+ secondLatLng = destinationOnLine(this._map, latlng, secondLatLng, this._pxRadiusToMeter(this.options.maxRadiusCircleMarker));
+ }
+ }
+ return secondLatLng;
+ },
+ _handleHintMarkerSnapping(){
+ if(this.options.editable) {
+ if (this._hintMarker._snapped) {
+ const latlng = this._centerMarker.getLatLng();
+ const secondLatLng = this._hintMarker.getLatLng();
+ const distance = this._map.project(latlng).distanceTo(this._map.project(secondLatLng));
+ if (this.options.minRadiusCircleMarker && distance < this.options.minRadiusCircleMarker) {
+ this._hintMarker.setLatLng(this._hintMarker._orgLatLng);
+ } else if (this.options.maxRadiusCircleMarker && distance > this.options.maxRadiusCircleMarker) {
+ this._hintMarker.setLatLng(this._hintMarker._orgLatLng);
+ }
+ }
+ // calculate the new latlng of marker if the snapped latlng radius is out of min/max
+ this._hintMarker.setLatLng(this._getNewDestinationOfHintMarker());
+ }
},
+ _pxRadiusToMeter(radius){
+ const center = this._centerMarker.getLatLng();
+ const pointA = this._map.project(center);
+ const pointB = L.point(pointA.x + radius, pointA.y);
+ return this._map.unproject(pointB).distanceTo(center);
+ }
});
diff --git a/src/js/Draw/L.PM.Draw.Cut.js b/src/js/Draw/L.PM.Draw.Cut.js
index 9b683999..5225cf9f 100644
--- a/src/js/Draw/L.PM.Draw.Cut.js
+++ b/src/js/Draw/L.PM.Draw.Cut.js
@@ -1,3 +1,8 @@
+import lineIntersect from "@turf/line-intersect";
+import lineSplit from "@turf/line-split";
+import booleanContains from "@turf/boolean-contains";
+import get from "lodash/get";
+import Utils from "../L.PM.Utils";
import Draw from './L.PM.Draw';
import {difference, intersect} from "../helpers/turfHelper";
@@ -7,23 +12,83 @@ Draw.Cut = Draw.Polygon.extend({
this._shape = 'Cut';
this.toolbarButtonName = 'cutPolygon';
},
- _cut(layer) {
+ _finishShape() {
+ this._editedLayers = [];
+ // if self intersection is not allowed, do not finish the shape!
+ if (!this.options.allowSelfIntersection) {
+ // Check if polygon intersects when is completed and the line between the last and the first point is drawn
+ this._handleSelfIntersection(true, this._layer.getLatLngs()[0]);
+
+ if (this._doesSelfIntersect) {
+ return;
+ }
+ }
+
+ const coords = this._layer.getLatLngs();
+ const polygonLayer = L.polygon(coords, this.options.pathOptions);
+ // readout information about the latlngs like snapping points
+ polygonLayer._latlngInfos = this._layer._latlngInfo;
+ this.cut(polygonLayer);
+
+ // clean up snapping states
+ this._cleanupSnapping();
+
+ // remove the first vertex from "other snapping layers"
+ this._otherSnapLayers.splice(this._tempSnapLayerIndex, 1);
+ delete this._tempSnapLayerIndex;
+
+ this._editedLayers.forEach(({layer, originalLayer}) =>{
+ // fire pm:cut on the cutted layer
+ Utils._fireEvent(originalLayer,'pm:cut', {
+ shape: this._shape,
+ layer,
+ originalLayer,
+ });
+
+ // fire pm:cut on the map
+ Utils._fireEvent(this._map,'pm:cut', {
+ shape: this._shape,
+ layer,
+ originalLayer,
+ });
+
+ // fire edit event after cut
+ Utils._fireEvent(originalLayer,'pm:edit', { layer: originalLayer, shape: originalLayer.pm.getShape()});
+ });
+ this._editedLayers = [];
+
+ // disable drawing
+ this.disable();
+ if(this.options.continueDrawing){
+ this.enable();
+ }
+ },
+ cut(layer) {
const all = this._map._layers;
+ // contains information about snapping points
+ const _latlngInfos = layer._latlngInfos || [];
// find all layers that intersect with `layer`, the just drawn cutting layer
const layers = Object.keys(all)
- // convert object to array
+ // convert object to array
.map(l => all[l])
// only layers handled by leaflet-geoman
.filter(l => l.pm)
- // only polygons
- .filter(l => l instanceof L.Polygon)
+ // only polyline instances
+ .filter(l => l instanceof L.Polyline)
// exclude the drawn one
.filter(l => l !== layer)
+ .filter(l => !this._layerGroup.hasLayer(l))
// only layers with intersections
.filter(l => {
try {
+ const lineInter = !!lineIntersect(layer.toGeoJSON(15), l.toGeoJSON(15)).features.length > 0;
+
+ if(lineInter){
+ return lineInter;
+ }
return !!intersect(layer.toGeoJSON(15), l.toGeoJSON(15));
+
} catch (e) {
/* eslint-disable-next-line no-console */
console.error('You cant cut polygons with self-intersections');
@@ -33,15 +98,42 @@ Draw.Cut = Draw.Polygon.extend({
// loop through all layers that intersect with the drawn (cutting) layer
layers.forEach(l => {
+ let newLayer;
+ if(l instanceof L.Polygon) { // Also for L.Rectangle
+ // easiest way to clone the complete latlngs without reference
+ newLayer = L.polygon(l.getLatLngs());
+ const coords = newLayer.getLatLngs();
+
+ // snapping points added to the layer, so borders are cutted correct
+ _latlngInfos.forEach((info)=>{
+ if(info && info.snapInfo) {
+ const {latlng} = info;
+ // get closest layer ( == input layer) with the closest segment to the intersection point
+ const closest = this._calcClosestLayer(latlng, [newLayer]);
+ if (closest && closest.segment && closest.distance < this.options.snapDistance) {
+ const {segment} = closest;
+ if (segment && segment.length === 2) {
+ const {indexPath, parentPath, newIndex} = Utils._getIndexFromSegment(coords, segment);
+ // define the coordsRing that is edited
+ const coordsRing = indexPath.length > 1 ? get(coords, parentPath) : coords;
+ coordsRing.splice(newIndex, 0, latlng);
+ }
+ }
+ }
+ });
+ }else{ // L.Polyline
+ newLayer = l;
+ }
+
// find layer difference
- const diff = difference(l.toGeoJSON(15), layer.toGeoJSON(15));
+ const diff = this._cutLayer(layer,newLayer);
// the resulting layer after the cut
let resultLayer = L.geoJSON(diff, l.options);
if (resultLayer.getLayers().length === 1) {
[resultLayer] = resultLayer.getLayers(); // prevent that a unnecessary layergroup is created
}
- const resultingLayer = resultLayer.addTo(this._map);
+ const resultingLayer = resultLayer.addTo(this._map.pm._getContainingLayer());
// give the new layer the original options
resultingLayer.pm.enable(this.options);
@@ -53,7 +145,9 @@ Draw.Cut = Draw.Polygon.extend({
// remove old layer and cutting layer
l.remove();
+ l.removeFrom(this._map.pm._getContainingLayer());
layer.remove();
+ layer.removeFrom(this._map.pm._getContainingLayer());
// Remove it only if it is a layergroup. It can be only not a layergroup if a layer exists
if (resultingLayer.getLayers && resultingLayer.getLayers().length === 0) {
@@ -68,50 +162,29 @@ Draw.Cut = Draw.Polygon.extend({
});
});
- },
- _finishShape() {
- this._editedLayers = [];
- // if self intersection is not allowed, do not finish the shape!
- if (!this.options.allowSelfIntersection) {
- this._handleSelfIntersection(false);
- if (this._doesSelfIntersect) {
- return;
+ },
+ _cutLayer(layer,l){
+ const fg = L.geoJSON();
+ let diff;
+ // cut
+ if (l instanceof L.Polygon) {
+ // find layer difference
+ diff = difference(l.toGeoJSON(15), layer.toGeoJSON(15));
+ } else {
+ // get splitted line to look which line part is coverd by the cut polygon
+ const lineDiff = lineSplit(l.toGeoJSON(15), layer.toGeoJSON(15));
+ if (!lineDiff) {
+ return null;
}
- }
-
- const coords = this._layer.getLatLngs();
- const polygonLayer = L.polygon(coords, this.options.pathOptions);
- this._cut(polygonLayer);
-
- // disable drawing
- this.disable();
-
- // clean up snapping states
- this._cleanupSnapping();
-
- // remove the first vertex from "other snapping layers"
- this._otherSnapLayers.splice(this._tempSnapLayerIndex, 1);
- delete this._tempSnapLayerIndex;
-
- this._editedLayers.forEach(({layer, originalLayer}) =>{
- // fire pm:cut on the cutted layer
- originalLayer.fire('pm:cut', {
- shape: this._shape,
- layer,
- originalLayer,
- });
-
- // fire pm:cut on the map
- this._map.fire('pm:cut', {
- shape: this._shape,
- layer,
- originalLayer,
+ L.geoJSON(lineDiff).getLayers().forEach((lay) => {
+ // add only parts to the map, they are not covered by the cut polygon
+ if (!booleanContains(layer.toGeoJSON(15), lay.toGeoJSON(15))) {
+ lay.addTo(fg);
+ }
});
-
- // fire edit event after cut
- originalLayer.fire('pm:edit', { layer: originalLayer, shape: originalLayer.pm.getShape()});
- });
- this._editedLayers = [];
+ diff = fg.toGeoJSON(15);
+ }
+ return diff;
},
});
diff --git a/src/js/Draw/L.PM.Draw.Line.js b/src/js/Draw/L.PM.Draw.Line.js
index fa852f54..2f6445d3 100644
--- a/src/js/Draw/L.PM.Draw.Line.js
+++ b/src/js/Draw/L.PM.Draw.Line.js
@@ -1,5 +1,6 @@
import kinks from '@turf/kinks';
import Draw from './L.PM.Draw';
+import Utils from "../L.PM.Utils";
import { getTranslation } from '../helpers';
@@ -89,19 +90,20 @@ Draw.Line = Draw.extend({
// sync the hintline with hint marker
this._hintMarker.on('move', this._syncHintLine, this);
- // fire drawstart event
- this._map.fire('pm:drawstart', {
- shape: this._shape,
- workingLayer: this._layer,
- });
- this._setGlobalDrawMode();
-
// toggle the draw button of the Toolbar in case drawing mode got enabled without the button
this._map.pm.Toolbar.toggleButton(this.toolbarButtonName, true);
// an array used in the snapping mixin.
// TODO: think about moving this somewhere else?
this._otherSnapLayers = [];
+
+
+ // fire drawstart event
+ Utils._fireEvent(this._map,'pm:drawstart', {
+ shape: this._shape,
+ workingLayer: this._layer,
+ });
+ this._setGlobalDrawMode();
},
disable() {
// disable draw mode
@@ -130,10 +132,6 @@ Draw.Line = Draw.extend({
// remove layer
this._map.removeLayer(this._layerGroup);
- // fire drawend event
- this._map.fire('pm:drawend', { shape: this._shape });
- this._setGlobalDrawMode();
-
// toggle the draw button of the Toolbar in case drawing mode got disabled without the button
this._map.pm.Toolbar.toggleButton(this.toolbarButtonName, false);
@@ -141,6 +139,11 @@ Draw.Line = Draw.extend({
if (this.options.snappable) {
this._cleanupSnapping();
}
+
+ // fire drawend event
+ Utils._fireEvent(this._map,'pm:drawend', { shape: this._shape });
+ this._setGlobalDrawMode();
+
},
enabled() {
return this._enabled;
@@ -152,11 +155,6 @@ Draw.Line = Draw.extend({
this.enable(options);
}
},
- hasSelfIntersection() {
- // check for self intersection of the layer and return true/false
- const selfIntersection = kinks(this._layer.toGeoJSON(15));
- return selfIntersection.features.length > 0;
- },
_syncHintLine() {
const polyPoints = this._layer.getLatLngs();
@@ -186,6 +184,11 @@ Draw.Line = Draw.extend({
this._handleSelfIntersection(true, e.latlng);
}
},
+ hasSelfIntersection() {
+ // check for self intersection of the layer and return true/false
+ const selfIntersection = kinks(this._layer.toGeoJSON(15));
+ return selfIntersection.features.length > 0;
+ },
_handleSelfIntersection(addVertex, latlng) {
// ok we need to check the self intersection here
// problem: during draw, the marker on the cursor is not yet part
@@ -219,33 +222,6 @@ Draw.Line = Draw.extend({
this._hintline.setStyle(this.options.hintlineStyle);
}
},
- _removeLastVertex() {
- // remove last coords
- const coords = this._layer.getLatLngs();
- const removedCoord = coords.pop();
-
- // if all coords are gone, cancel drawing
- if (coords.length < 1) {
- this.disable();
- return;
- }
-
- // find corresponding marker
- const marker = this._layerGroup
- .getLayers()
- .filter(l => l instanceof L.Marker)
- .filter(l => !L.DomUtil.hasClass(l._icon, 'cursor-marker'))
- .find(l => l.getLatLng() === removedCoord);
-
- // remove that marker
- this._layerGroup.removeLayer(marker);
-
- // update layer with new coords
- this._layer.setLatLngs(coords);
-
- // sync the hintline again
- this._syncHintLine();
- },
_createVertex(e) {
// don't create a vertex if we have a selfIntersection and it is not allowed
if (!this.options.allowSelfIntersection) {
@@ -279,18 +255,51 @@ Draw.Line = Draw.extend({
// is this the first point?
const first = this._layer.getLatLngs().length === 0;
+ this._layer._latlngInfo = this._layer._latlngInfo || [];
+ this._layer._latlngInfo.push({
+ latlng,
+ snapInfo: this._hintMarker._snapInfo
+ });
+
this._layer.addLatLng(latlng);
const newMarker = this._createMarker(latlng, first);
this._hintline.setLatLngs([latlng, latlng]);
- this._layer.fire('pm:vertexadded', {
+ Utils._fireEvent(this._layer,'pm:vertexadded', {
shape: this._shape,
workingLayer: this._layer,
marker: newMarker,
latlng,
});
},
+ _removeLastVertex() {
+ // remove last coords
+ const coords = this._layer.getLatLngs();
+ const removedCoord = coords.pop();
+
+ // if all coords are gone, cancel drawing
+ if (coords.length < 1) {
+ this.disable();
+ return;
+ }
+
+ // find corresponding marker
+ const marker = this._layerGroup
+ .getLayers()
+ .filter(l => l instanceof L.Marker)
+ .filter(l => !L.DomUtil.hasClass(l._icon, 'cursor-marker'))
+ .find(l => l.getLatLng() === removedCoord);
+
+ // remove that marker
+ this._layerGroup.removeLayer(marker);
+
+ // update layer with new coords
+ this._layer.setLatLngs(coords);
+
+ // sync the hintline again
+ this._syncHintLine();
+ },
_finishShape() {
// if self intersection is not allowed, do not finish the shape!
if (!this.options.allowSelfIntersection) {
@@ -311,16 +320,12 @@ Draw.Line = Draw.extend({
// create the leaflet shape and add it to the map
const polylineLayer = L.polyline(coords, this.options.pathOptions).addTo(
- this._map
+ this._map.pm._getContainingLayer()
);
- this._setShapeForFinishLayer(polylineLayer);
- this._addDrawnLayerProp(polylineLayer);
-
- // disable drawing
- this.disable();
+ this._finishLayer(polylineLayer);
// fire the pm:create event and pass shape and layer
- this._map.fire('pm:create', {
+ Utils._fireEvent(this._map,'pm:create', {
shape: this._shape,
layer: polylineLayer,
});
@@ -328,6 +333,12 @@ Draw.Line = Draw.extend({
if (this.options.snappable) {
this._cleanupSnapping();
}
+
+ // disable drawing
+ this.disable();
+ if(this.options.continueDrawing){
+ this.enable();
+ }
},
_createMarker(latlng, first) {
// create the new marker
diff --git a/src/js/Draw/L.PM.Draw.Marker.js b/src/js/Draw/L.PM.Draw.Marker.js
index b2cd8f99..d718e01d 100644
--- a/src/js/Draw/L.PM.Draw.Marker.js
+++ b/src/js/Draw/L.PM.Draw.Marker.js
@@ -1,6 +1,6 @@
import Draw from './L.PM.Draw';
-
import { getTranslation } from '../helpers';
+import Utils from "../L.PM.Utils";
Draw.Marker = Draw.extend({
initialize(map) {
@@ -46,19 +46,22 @@ Draw.Marker = Draw.extend({
// sync hint marker with mouse cursor
this._map.on('mousemove', this._syncHintMarker, this);
+
+ // enable edit mode for existing markers
+ if(this.options.markerEditable) {
+ this._map.eachLayer(layer => {
+ if (this.isRelevantMarker(layer)) {
+ layer.pm.enable();
+ }
+ });
+ }
+
// fire drawstart event
- this._map.fire('pm:drawstart', {
+ Utils._fireEvent(this._map,'pm:drawstart', {
shape: this._shape,
workingLayer: this._layer,
});
this._setGlobalDrawMode();
-
- // enable edit mode for existing markers
- this._map.eachLayer(layer => {
- if (this.isRelevantMarker(layer)) {
- layer.pm.enable();
- }
- });
},
disable() {
// cancel, if drawing mode isn't even enabled
@@ -85,9 +88,6 @@ Draw.Marker = Draw.extend({
}
});
- // fire drawend event
- this._map.fire('pm:drawend', { shape: this._shape });
- this._setGlobalDrawMode();
// toggle the draw button of the Toolbar in case drawing mode got disabled without the button
this._map.pm.Toolbar.toggleButton(this.toolbarButtonName, false);
@@ -97,9 +97,9 @@ Draw.Marker = Draw.extend({
this._cleanupSnapping();
}
- },
- isRelevantMarker(layer) {
- return layer instanceof L.Marker && layer.pm && !layer._pmTempLayer;
+ // fire drawend event
+ Utils._fireEvent(this._map,'pm:drawend', { shape: this._shape });
+ this._setGlobalDrawMode();
},
enabled() {
return this._enabled;
@@ -111,6 +111,20 @@ Draw.Marker = Draw.extend({
this.enable(options);
}
},
+ isRelevantMarker(layer) {
+ return layer instanceof L.Marker && layer.pm && !layer._pmTempLayer;
+ },
+ _syncHintMarker(e) {
+ // move the cursor marker
+ this._hintMarker.setLatLng(e.latlng);
+
+ // if snapping is enabled, do it
+ if (this.options.snappable) {
+ const fakeDragEvent = e;
+ fakeDragEvent.target = this._hintMarker;
+ this._handleSnapping(fakeDragEvent);
+ }
+ },
_createMarker(e) {
if (!e.latlng) {
return;
@@ -127,33 +141,36 @@ Draw.Marker = Draw.extend({
// create marker
const marker = new L.Marker(latlng, this.options.markerStyle);
- this._setShapeForFinishLayer(marker);
- this._addDrawnLayerProp(marker);
+ this._finishLayer(marker);
+
+ if(!marker.pm){
+ marker.options.draggable = false;
+ }
// add marker to the map
- marker.addTo(this._map);
+ marker.addTo(this._map.pm._getContainingLayer());
- // enable editing for the marker
- marker.pm.enable();
+
+ if(marker.pm && this.options.markerEditable) {
+ // enable editing for the marker
+ marker.pm.enable();
+ }else{
+ if(marker.dragging) {
+ marker.dragging.disable();
+ }
+ }
// fire the pm:create event and pass shape and marker
- this._map.fire('pm:create', {
+ Utils._fireEvent(this._map,'pm:create', {
shape: this._shape,
marker, // DEPRECATED
layer: marker,
});
this._cleanupSnapping();
- },
- _syncHintMarker(e) {
- // move the cursor marker
- this._hintMarker.setLatLng(e.latlng);
- // if snapping is enabled, do it
- if (this.options.snappable) {
- const fakeDragEvent = e;
- fakeDragEvent.target = this._hintMarker;
- this._handleSnapping(fakeDragEvent);
+ if(!this.options.continueDrawing){
+ this.disable();
}
},
});
diff --git a/src/js/Draw/L.PM.Draw.Polygon.js b/src/js/Draw/L.PM.Draw.Polygon.js
index 1320d28a..6cb40490 100644
--- a/src/js/Draw/L.PM.Draw.Polygon.js
+++ b/src/js/Draw/L.PM.Draw.Polygon.js
@@ -1,4 +1,5 @@
import Draw from './L.PM.Draw';
+import Utils from "../L.PM.Utils";
import { getTranslation } from '../helpers';
@@ -8,47 +9,6 @@ Draw.Polygon = Draw.Line.extend({
this._shape = 'Polygon';
this.toolbarButtonName = 'drawPolygon';
},
- _finishShape(e) {
- // if self intersection is not allowed, do not finish the shape!
- if (!this.options.allowSelfIntersection) {
- // Check if polygon intersects when is completed and the line between the last and the first point is drawn
- this._handleSelfIntersection(true, this._layer.getLatLngs()[0]);
-
- if (this._doesSelfIntersect) {
- return;
- }
- }
-
- // get coordinates
- const coords = this._layer.getLatLngs();
-
- // only finish the shape if there are 3 or more vertices
- if (coords.length <= 2) {
- return;
- }
-
- const polygonLayer = L.polygon(coords, this.options.pathOptions).addTo(
- this._map
- );
- this._setShapeForFinishLayer(polygonLayer);
- this._addDrawnLayerProp(polygonLayer);
-
- // disable drawing
- this.disable();
-
- // fire the pm:create event and pass shape and layer
- this._map.fire('pm:create', {
- shape: this._shape,
- layer: polygonLayer,
- });
-
- // clean up snapping states
- this._cleanupSnapping();
-
- // remove the first vertex from "other snapping layers"
- this._otherSnapLayers.splice(this._tempSnapLayerIndex, 1);
- delete this._tempSnapLayerIndex;
- },
_createMarker(latlng, first) {
// create the new marker
const marker = new L.Marker(latlng, {
@@ -93,4 +53,47 @@ Draw.Polygon = Draw.Line.extend({
return marker;
},
+ _finishShape(e) {
+ // if self intersection is not allowed, do not finish the shape!
+ if (!this.options.allowSelfIntersection) {
+ // Check if polygon intersects when is completed and the line between the last and the first point is drawn
+ this._handleSelfIntersection(true, this._layer.getLatLngs()[0]);
+
+ if (this._doesSelfIntersect) {
+ return;
+ }
+ }
+
+ // get coordinates
+ const coords = this._layer.getLatLngs();
+
+ // only finish the shape if there are 3 or more vertices
+ if (coords.length <= 2) {
+ return;
+ }
+
+ const polygonLayer = L.polygon(coords, this.options.pathOptions).addTo(
+ this._map.pm._getContainingLayer()
+ );
+ this._finishLayer(polygonLayer);
+
+ // fire the pm:create event and pass shape and layer
+ Utils._fireEvent(this._map,'pm:create', {
+ shape: this._shape,
+ layer: polygonLayer,
+ });
+
+ // clean up snapping states
+ this._cleanupSnapping();
+
+ // remove the first vertex from "other snapping layers"
+ this._otherSnapLayers.splice(this._tempSnapLayerIndex, 1);
+ delete this._tempSnapLayerIndex;
+
+ // disable drawing
+ this.disable();
+ if(this.options.continueDrawing){
+ this.enable();
+ }
+ },
});
diff --git a/src/js/Draw/L.PM.Draw.Rectangle.js b/src/js/Draw/L.PM.Draw.Rectangle.js
index f32dab6e..e8e52696 100644
--- a/src/js/Draw/L.PM.Draw.Rectangle.js
+++ b/src/js/Draw/L.PM.Draw.Rectangle.js
@@ -1,4 +1,5 @@
import Draw from './L.PM.Draw';
+import Utils from "../L.PM.Utils";
import { getTranslation } from '../helpers';
@@ -86,19 +87,20 @@ Draw.Rectangle = Draw.extend({
// sync hint marker with mouse cursor
this._map.on('mousemove', this._syncHintMarker, this);
- // fire drawstart event
- this._map.fire('pm:drawstart', {
- shape: this._shape,
- workingLayer: this._layer,
- });
- this._setGlobalDrawMode();
-
// toggle the draw button of the Toolbar in case drawing mode got enabled without the button
this._map.pm.Toolbar.toggleButton(this.toolbarButtonName, true);
// an array used in the snapping mixin.
// TODO: think about moving this somewhere else?
this._otherSnapLayers = [];
+
+ // fire drawstart event
+ Utils._fireEvent(this._map,'pm:drawstart', {
+ shape: this._shape,
+ workingLayer: this._layer,
+ });
+ this._setGlobalDrawMode();
+
},
disable() {
// disable drawing mode
@@ -121,10 +123,6 @@ Draw.Rectangle = Draw.extend({
// remove helping layers
this._map.removeLayer(this._layerGroup);
- // fire drawend event
- this._map.fire('pm:drawend', { shape: this._shape });
- this._setGlobalDrawMode();
-
// toggle the draw button of the Toolbar in case drawing mode got disabled without the button
this._map.pm.Toolbar.toggleButton(this.toolbarButtonName, false);
@@ -132,6 +130,10 @@ Draw.Rectangle = Draw.extend({
if (this.options.snappable) {
this._cleanupSnapping();
}
+ // fire drawend event
+ Utils._fireEvent(this._map,'pm:drawend', { shape: this._shape });
+ this._setGlobalDrawMode();
+
},
enabled() {
return this._enabled;
@@ -224,6 +226,16 @@ Draw.Rectangle = Draw.extend({
});
}
},
+ _findCorners() {
+ const corners = this._layer.getBounds();
+
+ const northwest = corners.getNorthWest();
+ const northeast = corners.getNorthEast();
+ const southeast = corners.getSouthEast();
+ const southwest = corners.getSouthWest();
+
+ return [northwest, northeast, southeast, southwest];
+ },
_finishShape(e) {
// assign the coordinate of the click to the hintMarker, that's necessary for
// mobile where the marker can't follow a cursor
@@ -239,28 +251,20 @@ Draw.Rectangle = Draw.extend({
// create the final rectangle layer, based on opposite corners A & B
const rectangleLayer = L.rectangle([A, B], this.options.pathOptions).addTo(
- this._map
+ this._map.pm._getContainingLayer()
);
- this._setShapeForFinishLayer(rectangleLayer);
- this._addDrawnLayerProp(rectangleLayer);
-
- // disable drawing
- this.disable();
+ this._finishLayer(rectangleLayer);
// fire the pm:create event and pass shape and layer
- this._map.fire('pm:create', {
+ Utils._fireEvent(this._map,'pm:create', {
shape: this._shape,
layer: rectangleLayer,
});
- },
- _findCorners() {
- const corners = this._layer.getBounds();
- const northwest = corners.getNorthWest();
- const northeast = corners.getNorthEast();
- const southeast = corners.getSouthEast();
- const southwest = corners.getSouthWest();
-
- return [northwest, northeast, southeast, southwest];
+ // disable drawing
+ this.disable();
+ if(this.options.continueDrawing){
+ this.enable();
+ }
},
});
diff --git a/src/js/Draw/L.PM.Draw.js b/src/js/Draw/L.PM.Draw.js
index eabdd1bf..0966f6c0 100644
--- a/src/js/Draw/L.PM.Draw.js
+++ b/src/js/Draw/L.PM.Draw.js
@@ -1,4 +1,5 @@
import SnapMixin from '../Mixins/Snapping';
+import Utils from "../L.PM.Utils";
const Draw = L.Class.extend({
includes: [SnapMixin],
@@ -17,12 +18,21 @@ const Draw = L.Class.extend({
},
markerStyle: {
draggable: true,
+ icon: L.icon()
},
+ markerEditable: true,
+ continueDrawing: false,
},
setOptions(options) {
L.Util.setOptions(this, options);
},
initialize(map) {
+ // Overwriting the default tooltipAnchor of the default Marker Icon, because the tooltip functionality was updated but not the anchor in the Icon
+ // Issue https://github.com/Leaflet/Leaflet/issues/7302 - Leaflet v1.7.1
+ const defaultIcon = new L.Icon.Default();
+ defaultIcon.options.tooltipAnchor = [0,0];
+ this.options.markerStyle.icon = defaultIcon;
+
// save the map
this._map = map;
@@ -33,6 +43,9 @@ const Draw = L.Class.extend({
this.shapes.forEach(shape => {
this[shape] = new L.PM.Draw[shape](this._map);
});
+
+ this.Marker.setOptions({continueDrawing: true});
+ this.CircleMarker.setOptions({continueDrawing: true});
},
setPathOptions(options) {
this.options.pathOptions = options;
@@ -83,17 +96,28 @@ const Draw = L.Class.extend({
_setGlobalDrawMode() {
// extended to all PM.Draw shapes
if (this._shape === "Cut") {
- this._map.fire('pm:globalcutmodetoggled', {
+ Utils._fireEvent(this._map,'pm:globalcutmodetoggled', {
enabled: !!this._enabled,
map: this._map,
});
} else {
- this._map.fire('pm:globaldrawmodetoggled', {
+ Utils._fireEvent(this._map,'pm:globaldrawmodetoggled', {
enabled: this._enabled,
shape: this._shape,
map: this._map,
});
}
+
+ const layers = Utils.findLayers(this._map);
+ if (this._enabled) {
+ layers.forEach((layer) => {
+ Utils.disablePopup(layer);
+ })
+ } else {
+ layers.forEach((layer) => {
+ Utils.enablePopup(layer);
+ })
+ }
},
createNewDrawInstance(name, jsClass) {
@@ -142,12 +166,18 @@ const Draw = L.Class.extend({
}
return this[name] ? this[name]._shape : name;
},
+ _finishLayer(layer){
+ // add the pm options from drawing to the new layer (edit)
+ layer.pm.setOptions(this.options);
+ // set the shape (can be a custom shape)
+ if(layer.pm) {
+ layer.pm._shape = this._shape;
+ }
+ this._addDrawnLayerProp(layer);
+ },
_addDrawnLayerProp(layer){
layer._drawnByGeoman = true;
},
- _setShapeForFinishLayer(layer){
- layer.pm._shape = this._shape;
- }
});
export default Draw;
diff --git a/src/js/Edit/L.PM.Edit.Circle.js b/src/js/Edit/L.PM.Edit.Circle.js
index 0fc9988c..1038617d 100644
--- a/src/js/Edit/L.PM.Edit.Circle.js
+++ b/src/js/Edit/L.PM.Edit.Circle.js
@@ -1,5 +1,6 @@
import Edit from './L.PM.Edit';
import Utils from "../L.PM.Utils";
+import {destinationOnLine} from "../helpers";
Edit.Circle = Edit.extend({
_shape: 'Circle',
@@ -9,37 +10,6 @@ Edit.Circle = Edit.extend({
// create polygon around the circle border
this._updateHiddenPolyCircle();
},
- applyOptions() {
- if (this.options.snappable) {
- this._initSnappableMarkers();
- // sync the hintline with hint marker
- this._outerMarker.on('move', this._syncHintLine, this);
- this._outerMarker.on('move', this._syncCircleRadius, this);
- this._centerMarker.on('move', this._moveCircle, this);
- } else {
- this._disableSnapping();
- }
- },
- _disableSnapping() {
- this._markers.forEach(marker => {
- marker.off('move', this._syncHintLine, this);
- marker.off('move', this._syncCircleRadius, this);
- marker.off('drag', this._handleSnapping, this);
- marker.off('dragend', this._cleanupSnapping, this);
- });
-
- this._layer.off('pm:dragstart', this._unsnap, this);
- },
- toggleEdit(options) {
- if (!this.enabled()) {
- this.enable(options);
- } else {
- this.disable();
- }
- },
- enabled() {
- return this._enabled;
- },
enable(options) {
L.Util.setOptions(this, options);
@@ -59,8 +29,6 @@ Edit.Circle = Edit.extend({
this.applyOptions();
- this._layer.fire('pm:enable', { layer: this._layer, shape: this.getShape() });
-
// if polygon gets removed from map, disable edit mode
this._layer.on('remove', e => {
this.disable(e.target);
@@ -68,6 +36,7 @@ Edit.Circle = Edit.extend({
// create polygon around the circle border
this._updateHiddenPolyCircle();
+ Utils._fireEvent(this._layer,'pm:enable', { layer: this._layer, shape: this.getShape() });
},
disable(layer = this._layer) {
// if it's not enabled, it doesn't need to be disabled
@@ -83,6 +52,7 @@ Edit.Circle = Edit.extend({
this._centerMarker.off('dragstart', this._fireDragStart, this);
this._centerMarker.off('drag', this._fireDrag, this);
this._centerMarker.off('dragend', this._fireDragEnd, this);
+ this._outerMarker.off('drag',this._handleOuterMarkerSnapping, this);
layer.pm._enabled = false;
layer.pm._helperLayers.clearLayers();
@@ -94,15 +64,25 @@ Edit.Circle = Edit.extend({
const el = layer._path ? layer._path : this._layer._renderer._container;
L.DomUtil.removeClass(el, 'leaflet-pm-draggable');
- this._layer.fire('pm:disable', { layer: this._layer, shape: this.getShape() });
if (this._layerEdited) {
- this._layer.fire('pm:update', { layer: this._layer, shape: this.getShape() });
+ Utils._fireEvent(this._layer,'pm:update', { layer: this._layer, shape: this.getShape() });
}
this._layerEdited = false;
+ Utils._fireEvent(this._layer,'pm:disable', { layer: this._layer, shape: this.getShape() });
return true;
},
+ enabled() {
+ return this._enabled;
+ },
+ toggleEdit(options) {
+ if (!this.enabled()) {
+ this.enable(options);
+ } else {
+ this.disable();
+ }
+ },
_initMarkers() {
const map = this._map;
@@ -126,72 +106,19 @@ Edit.Circle = Edit.extend({
this._outerMarker = this._createOuterMarker(outer);
this._markers = [this._centerMarker, this._outerMarker];
this._createHintLine(this._centerMarker, this._outerMarker);
-
-
- },
- _getLatLngOnCircle(center, radius) {
- const pointA = this._map.project(center);
- const pointB = L.point(pointA.x + radius, pointA.y);
-
- return this._map.unproject(pointB);
},
- _resizeCircle() {
- this._syncHintLine();
- this._syncCircleRadius();
- },
- _moveCircle(e) {
- const center = e.latlng;
- this._layer.setLatLng(center);
-
- const radius = this._layer._radius;
-
- const outer = this._getLatLngOnCircle(center, radius);
- this._outerMarker.setLatLng(outer);
- this._syncHintLine();
-
- this._updateHiddenPolyCircle();
-
- this._layer.fire('pm:centerplaced', {
- layer: this._layer,
- latlng: center,
- shape: this.getShape()
- });
- },
- _onMarkerDragStart(e) {
- this._layer.fire('pm:markerdragstart', {
- layer: this._layer,
- markerEvent: e,
- shape: this.getShape(),
- indexPath: undefined
- });
- },
- _onMarkerDragEnd(e) {
- // fire edit event
- this._fireEdit();
-
- // fire markerdragend event
- this._layer.fire('pm:markerdragend', {
- layer: this._layer,
- markerEvent: e,
- shape: this.getShape(),
- indexPath: undefined
- });
- },
- _syncCircleRadius() {
- const A = this._centerMarker.getLatLng();
- const B = this._outerMarker.getLatLng();
-
- const distance = A.distanceTo(B);
-
- this._layer.setRadius(distance);
- this._updateHiddenPolyCircle();
- },
- _syncHintLine() {
- const A = this._centerMarker.getLatLng();
- const B = this._outerMarker.getLatLng();
-
- // set coords for hintline from marker to last vertex of drawin polyline
- this._hintline.setLatLngs([A, B]);
+ applyOptions() {
+ if (this.options.snappable) {
+ this._initSnappableMarkers();
+ // update marker latlng when snapped latlng radius is out of min/max
+ this._outerMarker.on('drag',this._handleOuterMarkerSnapping, this);
+ // sync the hintline with hint marker
+ this._outerMarker.on('move', this._syncHintLine, this);
+ this._outerMarker.on('move', this._syncCircleRadius, this);
+ this._centerMarker.on('move', this._moveCircle, this);
+ } else {
+ this._disableSnapping();
+ }
},
_createHintLine(markerA, markerB) {
const A = markerA.getLatLng();
@@ -232,25 +159,110 @@ Edit.Circle = Edit.extend({
marker._pmTempLayer = true;
marker.on('dragstart', this._onMarkerDragStart, this);
+ marker.on('drag', this._onMarkerDrag, this);
marker.on('dragend', this._onMarkerDragEnd, this);
this._helperLayers.addLayer(marker);
return marker;
},
+ _resizeCircle() {
+ this._outerMarker.setLatLng(this._getNewDestinationOfOuterMarker());
+ this._syncHintLine();
+ this._syncCircleRadius();
+ },
+ _moveCircle(e) {
+ const center = e.latlng;
+ this._layer.setLatLng(center);
+
+ const radius = this._layer._radius;
+
+ const outer = this._getLatLngOnCircle(center, radius);
+ this._outerMarker.setLatLng(outer);
+ this._syncHintLine();
+
+ this._updateHiddenPolyCircle();
+
+ Utils._fireEvent(this._layer,'pm:centerplaced', {
+ layer: this._layer,
+ latlng: center,
+ shape: this.getShape()
+ });
+ },
+ _syncCircleRadius() {
+ const A = this._centerMarker.getLatLng();
+ const B = this._outerMarker.getLatLng();
+
+ const distance = A.distanceTo(B);
+
+ if(this.options.minRadiusCircle && distance < this.options.minRadiusCircle) {
+ this._layer.setRadius(this.options.minRadiusCircle);
+ }else if(this.options.maxRadiusCircle && distance > this.options.maxRadiusCircle) {
+ this._layer.setRadius(this.options.maxRadiusCircle);
+ }else{
+ this._layer.setRadius(distance);
+ }
+
+ this._updateHiddenPolyCircle();
+ },
+ _syncHintLine() {
+ const A = this._centerMarker.getLatLng();
+ const B = this._outerMarker.getLatLng();
+
+ // set coords for hintline from marker to last vertex of drawin polyline
+ this._hintline.setLatLngs([A, B]);
+ },
+ _disableSnapping() {
+ this._markers.forEach(marker => {
+ marker.off('move', this._syncHintLine, this);
+ marker.off('move', this._syncCircleRadius, this);
+ marker.off('drag', this._handleSnapping, this);
+ marker.off('dragend', this._cleanupSnapping, this);
+ });
+
+ this._layer.off('pm:dragstart', this._unsnap, this);
+ },
+ _onMarkerDragStart(e) {
+ Utils._fireEvent(this._layer,'pm:markerdragstart', {
+ layer: this._layer,
+ markerEvent: e,
+ shape: this.getShape(),
+ indexPath: undefined
+ });
+ },
+ _onMarkerDrag(e) {
+ Utils._fireEvent(this._layer,'pm:markerdrag', {
+ layer: this._layer,
+ markerEvent: e,
+ shape: this.getShape(),
+ indexPath: undefined
+ });
+ },
+ _onMarkerDragEnd(e) {
+ // fire edit event
+ this._fireEdit();
+
+ // fire markerdragend event
+ Utils._fireEvent(this._layer,'pm:markerdragend', {
+ layer: this._layer,
+ markerEvent: e,
+ shape: this.getShape(),
+ indexPath: undefined
+ });
+ },
_fireEdit() {
// fire edit event
- this._layer.fire('pm:edit', { layer: this._layer, shape: this.getShape() });
+ Utils._fireEvent(this._layer,'pm:edit', { layer: this._layer, shape: this.getShape() });
this._layerEdited = true;
},
_fireDragStart() {
- this._layer.fire('pm:dragstart', { layer: this._layer, shape: this.getShape() });
+ Utils._fireEvent(this._layer,'pm:dragstart', { layer: this._layer, shape: this.getShape() });
},
_fireDrag(e) {
- this._layer.fire('pm:drag', Object.assign({},e, {shape:this.getShape()}));
+ Utils._fireEvent(this._layer,'pm:drag', Object.assign({},e, {shape:this.getShape()}));
},
_fireDragEnd() {
- this._layer.fire('pm:dragend', { layer: this._layer, shape: this.getShape() });
+ Utils._fireEvent(this._layer,'pm:dragend', { layer: this._layer, shape: this.getShape() });
},
_updateHiddenPolyCircle() {
if (this._hiddenPolyCircle) {
@@ -262,5 +274,36 @@ Edit.Circle = Edit.extend({
if (!this._hiddenPolyCircle._parentCopy) {
this._hiddenPolyCircle._parentCopy = this._layer
}
+ },
+ _getLatLngOnCircle(center, radius) {
+ const pointA = this._map.project(center);
+ const pointB = L.point(pointA.x + radius, pointA.y);
+
+ return this._map.unproject(pointB);
+ },
+ _getNewDestinationOfOuterMarker(){
+ const latlng = this._centerMarker.getLatLng();
+ let secondLatLng = this._outerMarker.getLatLng();
+ const distance = latlng.distanceTo(secondLatLng);
+ if(this.options.minRadiusCircle && distance < this.options.minRadiusCircle) {
+ secondLatLng = destinationOnLine(this._map,latlng,secondLatLng,this.options.minRadiusCircle);
+ }else if(this.options.maxRadiusCircle && distance > this.options.maxRadiusCircle) {
+ secondLatLng = destinationOnLine(this._map,latlng,secondLatLng,this.options.maxRadiusCircle);
+ }
+ return secondLatLng;
+ },
+ _handleOuterMarkerSnapping(){
+ if(this._outerMarker._snapped) {
+ const latlng = this._centerMarker.getLatLng();
+ const secondLatLng = this._outerMarker.getLatLng();
+ const distance = latlng.distanceTo(secondLatLng);
+ if(this.options.minRadiusCircle && distance < this.options.minRadiusCircle) {
+ this._outerMarker.setLatLng(this._outerMarker._orgLatLng);
+ } else if(this.options.maxRadiusCircle && distance > this.options.maxRadiusCircle) {
+ this._outerMarker.setLatLng(this._outerMarker._orgLatLng);
+ }
+ }
+ // calculate the new latlng of marker if radius is out of min/max
+ this._outerMarker.setLatLng(this._getNewDestinationOfOuterMarker());
}
});
diff --git a/src/js/Edit/L.PM.Edit.CircleMarker.js b/src/js/Edit/L.PM.Edit.CircleMarker.js
index c2667c0b..5936345c 100644
--- a/src/js/Edit/L.PM.Edit.CircleMarker.js
+++ b/src/js/Edit/L.PM.Edit.CircleMarker.js
@@ -1,5 +1,6 @@
import Edit from './L.PM.Edit';
import Utils from "../L.PM.Utils";
+import {destinationOnLine} from "../helpers";
Edit.CircleMarker = Edit.extend({
_shape: 'CircleMarker',
@@ -9,54 +10,6 @@ Edit.CircleMarker = Edit.extend({
// create polygon around the circle border
this._updateHiddenPolyCircle();
},
- applyOptions() {
- // Use the not editable and only draggable version
- if (!this.options.editable && this.options.draggable) {
- this.enableLayerDrag();
- } else {
- this.disableLayerDrag();
- }
-
- // Make it editable like a Circle
- if (this.options.editable) {
- this._initMarkers();
- this._map.on('move', this._syncMarkers, this);
- } else {
- // only update the circle border poly
- this._map.on('move', this._updateHiddenPolyCircle, this);
- }
-
- // init snapping in different ways
- if (this.options.snappable) {
- if (this.options.editable) {
- this._initSnappableMarkers();
- // sync the hintline with hint marker
- this._outerMarker.on('move', this._syncHintLine, this);
- this._outerMarker.on('move', this._syncCircleRadius, this);
- } else {
- this._initSnappableMarkersDrag();
- }
- } else if (this.options.editable) {
- this._disableSnapping();
- } else {
- this._disableSnappingDrag();
- }
-
- // enable removal for the marker
- if (!this.options.preventMarkerRemoval) {
- this._layer.on('contextmenu', this._removeMarker, this);
- }
- },
- toggleEdit(options) {
- if (!this.enabled()) {
- this.enable(options);
- } else {
- this.disable();
- }
- },
- enabled() {
- return this._enabled;
- },
enable(options = { draggable: true, snappable: true }) {
L.Util.setOptions(this, options);
@@ -67,21 +20,23 @@ Edit.CircleMarker = Edit.extend({
return;
}
- if (!this.enabled()) {
+ if (this.enabled()) {
// if it was already enabled, disable first
// we don't block enabling again because new options might be passed
this.disable();
}
this.applyOptions();
- this._layer.fire('pm:enable', { layer: this._layer, shape: this.getShape() });
// change state
this._enabled = true;
+ this._layer.on('pm:dragstart', this._onDragStart, this);
this._layer.on('pm:dragend', this._onMarkerDragEnd, this);
// create polygon around the circle border
this._updateHiddenPolyCircle();
+
+ Utils._fireEvent(this._layer,'pm:enable', { layer: this._layer, shape: this.getShape() });
},
disable(layer = this._layer) {
// prevent disabling if layer is being dragged
@@ -100,28 +55,83 @@ Edit.CircleMarker = Edit.extend({
if (this.options.editable) {
this._map.off('move', this._syncMarkers, this);
+ if(this._outerMarker) {
+ // update marker latlng when snapped latlng radius is out of min/max
+ this._outerMarker.on('drag', this._handleOuterMarkerSnapping, this);
+ }
} else {
this._map.off('move', this._updateHiddenPolyCircle, this);
}
// disable dragging, as this could have been active even without being enabled
this.disableLayerDrag();
+ this._layer.off('contextmenu', this._removeMarker, this);
+
// only fire events if it was enabled before
if (!this.enabled()) {
- this._layer.fire('pm:disable', { layer: this._layer, shape: this.getShape() });
-
if (this._layerEdited) {
- this._layer.fire('pm:update', { layer: this._layer, shape: this.getShape() });
+ Utils._fireEvent(this._layer,'pm:update', { layer: this._layer, shape: this.getShape() });
}
this._layerEdited = false;
+ Utils._fireEvent(this._layer,'pm:disable', { layer: this._layer, shape: this.getShape() });
}
- this._layer.off('contextmenu', this._removeMarker, this);
-
layer.pm._enabled = false;
return true;
},
+ enabled() {
+ return this._enabled;
+ },
+ toggleEdit(options) {
+ if (!this.enabled()) {
+ this.enable(options);
+ } else {
+ this.disable();
+ }
+ },
+ applyOptions() {
+ // Use the not editable and only draggable version
+ if (!this.options.editable && this.options.draggable) {
+ this.enableLayerDrag();
+ } else {
+ this.disableLayerDrag();
+ }
+
+ // Make it editable like a Circle
+ if (this.options.editable) {
+ this._initMarkers();
+ this._map.on('move', this._syncMarkers, this);
+ } else {
+ // only update the circle border poly
+ this._map.on('move', this._updateHiddenPolyCircle, this);
+ }
+
+ // init snapping in different ways
+ if (this.options.snappable) {
+ if (this.options.editable) {
+ this._initSnappableMarkers();
+ if(this.options.editable){
+ // update marker latlng when snapped latlng radius is out of min/max
+ this._outerMarker.on('drag',this._handleOuterMarkerSnapping, this);
+ }
+ // sync the hintline with hint marker
+ this._outerMarker.on('move', this._syncHintLine, this);
+ this._outerMarker.on('move', this._syncCircleRadius, this);
+ } else {
+ this._initSnappableMarkersDrag();
+ }
+ } else if (this.options.editable) {
+ this._disableSnapping();
+ } else {
+ this._disableSnappingDrag();
+ }
+
+ // enable removal for the marker
+ if (!this.options.preventMarkerRemoval) {
+ this._layer.on('contextmenu', this._removeMarker, this);
+ }
+ },
_initMarkers() {
const map = this._map;
@@ -170,21 +180,6 @@ Edit.CircleMarker = Edit.extend({
}
return marker;
},
- _moveCircle(e) {
- const center = e.latlng;
- this._layer.setLatLng(center);
-
- const radius = this._layer._radius;
- const outer = this._getLatLngOnCircle(center, radius);
- this._outerMarker.setLatLng(outer);
- this._syncHintLine();
-
- this._layer.fire('pm:centerplaced', {
- layer: this._layer,
- latlng: center,
- shape: this.getShape()
- });
- },
_createOuterMarker(latlng) {
const marker = this._createMarker(latlng);
marker.on('drag', this._resizeCircle, this);
@@ -200,12 +195,28 @@ Edit.CircleMarker = Edit.extend({
marker._pmTempLayer = true;
marker.on('dragstart', this._onMarkerDragStart, this);
+ marker.on('drag', this._onMarkerDrag, this);
marker.on('dragend', this._onMarkerDragEnd, this);
this._helperLayers.addLayer(marker);
return marker;
},
+ _moveCircle(e) {
+ const center = e.latlng;
+ this._layer.setLatLng(center);
+
+ const radius = this._layer._radius;
+ const outer = this._getLatLngOnCircle(center, radius);
+ this._outerMarker.setLatLng(outer);
+ this._syncHintLine();
+
+ Utils._fireEvent(this._layer,'pm:centerplaced', {
+ layer: this._layer,
+ latlng: center,
+ shape: this.getShape()
+ });
+ },
_syncMarkers() {
const center = this._layer.getLatLng();
const radius = this._layer._radius;
@@ -216,6 +227,7 @@ Edit.CircleMarker = Edit.extend({
this._updateHiddenPolyCircle();
},
_resizeCircle() {
+ this._outerMarker.setLatLng(this._getNewDestinationOfOuterMarker());
this._syncHintLine();
this._syncCircleRadius();
},
@@ -224,50 +236,63 @@ Edit.CircleMarker = Edit.extend({
const B = this._outerMarker.getLatLng();
const distance = this._map.project(A).distanceTo(this._map.project(B));
+ if(this.options.minRadiusCircleMarker && distance < this.options.minRadiusCircleMarker) {
+ this._layer.setRadius(this.options.minRadiusCircleMarker);
+ }else if(this.options.maxRadiusCircleMarker && distance > this.options.maxRadiusCircleMarker) {
+ this._layer.setRadius(this.options.maxRadiusCircleMarker);
+ }else{
+ this._layer.setRadius(distance);
+ }
- this._layer.setRadius(distance);
this._updateHiddenPolyCircle();
},
_syncHintLine() {
const A = this._centerMarker.getLatLng();
const B = this._outerMarker.getLatLng();
-
// set coords for hintline from marker to last vertex of drawin polyline
this._hintline.setLatLngs([A, B]);
},
- _moveMarker(e) {
- const center = e.latlng;
- this._layer.setLatLng(center).redraw();
- },
_removeMarker() {
if (this.options.editable) {
this.disable();
}
this._layer.remove();
- this._layer.fire('pm:remove', { layer: this._layer, shape: this.getShape() });
- this._map.fire('pm:remove', { layer: this._layer, shape: this.getShape() });
+ Utils._fireEvent(this._layer,'pm:remove', { layer: this._layer, shape: this.getShape() });
+ Utils._fireEvent(this._map,'pm:remove', { layer: this._layer, shape: this.getShape() });
+ },
+ _onDragStart(){
+ this._map.pm.Draw.CircleMarker._layerIsDragging = true;
},
_onMarkerDragStart(e) {
- this._layer.fire('pm:markerdragstart', {
+ Utils._fireEvent(this._layer,'pm:markerdragstart', {
markerEvent: e,
layer: this._layer,
shape: this.getShape(),
indexPath: undefined
});
},
- _fireEdit() {
- // fire edit event
- this._layer.fire('pm:edit', { layer: this._layer, shape: this.getShape() });
- this._layerEdited = true;
+ _onMarkerDrag(e) {
+ Utils._fireEvent(this._layer,'pm:markerdrag', {
+ layer: this._layer,
+ markerEvent: e,
+ shape: this.getShape(),
+ indexPath: undefined
+ });
},
_onMarkerDragEnd(e) {
- this._layer.fire('pm:markerdragend', {
+ this._map.pm.Draw.CircleMarker._layerIsDragging = false;
+ Utils._fireEvent(this._layer,'pm:markerdragend', {
layer: this._layer,
markerEvent: e,
shape: this.getShape(),
indexPath: undefined
});
},
+ _fireEdit() {
+ // fire edit event
+ Utils._fireEvent(this._layer,'pm:edit', { layer: this._layer, shape: this.getShape() });
+ this._layerEdited = true;
+ },
// _initSnappableMarkers when option editable is not true
_initSnappableMarkersDrag() {
const marker = this._layer;
@@ -311,5 +336,36 @@ Edit.CircleMarker = Edit.extend({
this._hiddenPolyCircle._parentCopy = this._layer
}
}
+ },
+ _getNewDestinationOfOuterMarker(){
+ const latlng = this._centerMarker.getLatLng();
+ let secondLatLng = this._outerMarker.getLatLng();
+ const distance = this._map.project(latlng).distanceTo(this._map.project(secondLatLng));
+ if(this.options.minRadiusCircleMarker && distance < this.options.minRadiusCircleMarker) {
+ secondLatLng = destinationOnLine(this._map,latlng,secondLatLng,this._pxRadiusToMeter(this.options.minRadiusCircleMarker));
+ }else if(this.options.maxRadiusCircleMarker && distance > this.options.maxRadiusCircleMarker) {
+ secondLatLng = destinationOnLine(this._map,latlng,secondLatLng,this._pxRadiusToMeter(this.options.maxRadiusCircleMarker));
+ }
+ return secondLatLng;
+ },
+ _handleOuterMarkerSnapping(){
+ if(this._outerMarker._snapped) {
+ const latlng = this._centerMarker.getLatLng();
+ const secondLatLng = this._outerMarker.getLatLng();
+ const distance = this._map.project(latlng).distanceTo(this._map.project(secondLatLng));
+ if(this.options.minRadiusCircleMarker && distance < this.options.minRadiusCircleMarker) {
+ this._outerMarker.setLatLng(this._outerMarker._orgLatLng);
+ } else if(this.options.maxRadiusCircleMarker && distance > this.options.maxRadiusCircleMarker) {
+ this._outerMarker.setLatLng(this._outerMarker._orgLatLng);
+ }
+ }
+ // calculate the new latlng of marker if radius is out of min/max
+ this._outerMarker.setLatLng(this._getNewDestinationOfOuterMarker());
+ },
+ _pxRadiusToMeter(radius){
+ const center = this._centerMarker.getLatLng();
+ const pointA = this._map.project(center);
+ const pointB = L.point(pointA.x + radius, pointA.y);
+ return this._map.unproject(pointB).distanceTo(center);
}
});
diff --git a/src/js/Edit/L.PM.Edit.ImageOverlay.js b/src/js/Edit/L.PM.Edit.ImageOverlay.js
new file mode 100644
index 00000000..9de25642
--- /dev/null
+++ b/src/js/Edit/L.PM.Edit.ImageOverlay.js
@@ -0,0 +1,74 @@
+import Edit from './L.PM.Edit';
+import Utils from "../L.PM.Utils";
+
+Edit.ImageOverlay = Edit.extend({
+ _shape: 'ImageOverlay',
+ initialize(layer) {
+ this._layer = layer;
+ this._enabled = false;
+ },
+ toggleEdit(options) {
+ if (!this.enabled()) {
+ this.enable(options);
+ } else {
+ this.disable();
+ }
+ },
+ enabled() {
+ return this._enabled;
+ },
+ enable(options = { draggable: true, snappable: true }) {
+ L.Util.setOptions(this, options);
+ this._map = this._layer._map;
+ // cancel when map isn't available, this happens when the polygon is removed before this fires
+ if (!this._map) {
+ return;
+ }
+ if (!this.enabled()) {
+ // if it was already enabled, disable first
+ // we don't block enabling again because new options might be passed
+ this.disable();
+ }
+
+ this.enableLayerDrag();
+
+ // change state
+ this._enabled = true;
+
+ // create markers for four corners of ImageOverlay
+ this._otherSnapLayers = L.PM.Edit.Rectangle.prototype._findCorners.apply(this);
+
+ Utils._fireEvent(this._layer,'pm:enable', { layer: this._layer, shape: this.getShape() });
+ },
+ disable(layer = this._layer) {
+ // prevent disabling if layer is being dragged
+ if (layer.pm._dragging) {
+ return false;
+ }
+
+ // Add map if it is not already set. This happens when disable() is called before enable()
+ if (!this._map) {
+ this._map = this._layer._map;
+ }
+ // disable dragging, as this could have been active even without being enabled
+ this.disableLayerDrag();
+
+ // only fire events if it was enabled before
+ if (!this.enabled()) {
+ if (this._layerEdited) {
+ Utils._fireEvent(layer,'pm:update', { layer, shape: this.getShape() });
+ }
+ this._layerEdited = false;
+ Utils._fireEvent(layer,'pm:disable', { layer, shape: this.getShape() });
+ }
+
+ this._layer.off('contextmenu', this._removeMarker, this);
+ layer.pm._enabled = false;
+ return true;
+ },
+ _fireEdit() {
+ // fire edit event
+ Utils._fireEvent(this._layer,'pm:edit', { layer: this._layer, shape: this.getShape() });
+ this._layerEdited = true;
+ },
+});
diff --git a/src/js/Edit/L.PM.Edit.LayerGroup.js b/src/js/Edit/L.PM.Edit.LayerGroup.js
index 79e6f282..f162f1c3 100644
--- a/src/js/Edit/L.PM.Edit.LayerGroup.js
+++ b/src/js/Edit/L.PM.Edit.LayerGroup.js
@@ -14,105 +14,126 @@ Edit.LayerGroup = L.Class.extend({
// if a new layer is added to the group, reinitialize
// This only works for FeatureGroups, not LayerGroups
// https://github.com/Leaflet/Leaflet/issues/4861
- this._layerGroup.on('layeradd', e => {
+
+ const addThrottle = (e) => {
if (e.target._pmTempLayer) {
return;
}
-
this._layers = this.findLayers();
- // init the newly added layer
- if (e.layer.pm) {
- this._initLayer(e.layer);
- }
+ const _initLayers = this._layers.filter((layer) => !layer.pm._parentLayerGroup || !(this._layerGroup._leaflet_id in layer.pm._parentLayerGroup));
+ // init the newly added layers (can be multiple because of the throttle)
+ _initLayers.forEach((layer) => {
+ this._initLayer(layer);
+ });
// if editing was already enabled for this group, enable it again
// so the new layers are enabled
- if (e.target.pm.enabled()) {
- this.enable(this.getOptions());
+ if (_initLayers.length > 0) {
+ if (this.enabled()) {
+ this.enable(this.getOptions());
+ }
}
- });
+ };
+ this._layerGroup.on('layeradd', L.Util.throttle(addThrottle, 100, this), this);
+
+ // Remove the layergroup from the layer
+ this._layerGroup.on('layerremove', (e) => {
+ this._removeLayerFromGroup(e.target);
+ }, this);
- // if a layer is removed from the group, calc the layers list again
- this._layerGroup.on('layerremove', e => {
+ const removeThrottle = (e) => {
if (e.target._pmTempLayer) {
return;
}
-
this._layers = this.findLayers();
- })
+ };
+ // if a layer is removed from the group, calc the layers list again.
+ // we run this as throttle because the findLayers() is a larger function
+ this._layerGroup.on('layerremove', L.Util.throttle(removeThrottle, 100, this), this);
},
- findLayers() {
- // get all layers of the layer group
- let layers = this._layerGroup.getLayers();
-
- // filter out layers that are no layerGroup
- layers = layers.filter(layer => !(layer instanceof L.LayerGroup));
-
- // filter out layers that don't have leaflet-geoman
- layers = layers.filter(layer => !!layer.pm);
-
- // filter out everything that's leaflet-geoman specific temporary stuff
- layers = layers.filter(layer => !layer._pmTempLayer);
-
- // return them
- return layers;
+ enable(options, _layerIds = []) {
+ this._options = options;
+ this._layers.forEach(layer => {
+ if (layer instanceof L.LayerGroup) {
+ if (_layerIds.indexOf(layer._leaflet_id) === -1) {
+ _layerIds.push(layer._leaflet_id);
+ layer.pm.enable(options, _layerIds);
+ }
+ } else {
+ layer.pm.enable(options);
+ }
+ });
},
- _initLayer(layer) {
- // available events
- const availableEvents = [
- 'pm:edit',
- 'pm:update',
- 'pm:enable',
- 'pm:disable',
- 'pm:remove',
- 'pm:dragstart',
- 'pm:drag',
- 'pm:dragend',
- 'pm:snap',
- 'pm:unsnap',
- 'pm:cut',
- 'pm:intersect',
- 'pm:markerdragend',
- 'pm:markerdragstart',
- 'pm:vertexadded',
- 'pm:vertexremoved',
- 'pm:centerplaced',
- ];
+ disable(_layerIds = []) {
+ this._layers.forEach(layer => {
+ if (layer instanceof L.LayerGroup) {
+ if (_layerIds.indexOf(layer._leaflet_id) === -1) {
+ _layerIds.push(layer._leaflet_id);
+ layer.pm.disable(_layerIds);
+ }
+ } else {
+ layer.pm.disable();
+ }
+ });
+ },
+ enabled(_layerIds = []) {
- // listen to the events of the layers in this group
- availableEvents.forEach(event => {
- layer.on(event, this._fireEvent, this);
+ const enabled = this._layers.find((layer) => {
+ if (layer instanceof L.LayerGroup) {
+ if (_layerIds.indexOf(layer._leaflet_id) === -1) {
+ _layerIds.push(layer._leaflet_id);
+ return layer.pm.enabled(_layerIds);
+ }
+ return false; // enabled is already returned because this is not the first time, so we can return always false
+ } else {
+ return layer.pm.enabled();
+ }
});
- // add reference for the group to each layer inside said group
- layer.pm._layerGroup = this._layerGroup;
- },
- _fireEvent(e) {
- this._layerGroup.fireEvent(e.type, e);
+ return !!enabled;
},
- toggleEdit(options) {
+ toggleEdit(options, _layerIds = []) {
this._options = options;
this._layers.forEach(layer => {
- layer.pm.toggleEdit(options);
+ if (layer instanceof L.LayerGroup) {
+ if (_layerIds.indexOf(layer._leaflet_id) === -1) {
+ _layerIds.push(layer._leaflet_id);
+ layer.pm.toggleEdit(options, _layerIds);
+ }
+ } else {
+ layer.pm.toggleEdit(options);
+ }
});
},
- enable(options) {
- this._options = options;
- this._layers.forEach(layer => {
- layer.pm.enable(options);
- });
+ _initLayer(layer) {
+ // add reference for the group to each layer inside said group by id, a layer can have multiple groups
+ const id = L.Util.stamp(this._layerGroup);
+ if (!layer.pm._parentLayerGroup) {
+ layer.pm._parentLayerGroup = {};
+ }
+ layer.pm._parentLayerGroup[id] = this._layerGroup;
},
- disable() {
- this._layers.forEach(layer => {
- layer.pm.disable();
- });
+ _removeLayerFromGroup(layer) {
+ if(layer.pm && layer.pm._layerGroup) {
+ const id = L.Util.stamp(this._layerGroup);
+ delete layer.pm._layerGroup[id];
+ }
},
- enabled() {
- const enabled = this._layers.find(layer => layer.pm.enabled());
- return !!enabled;
+ findLayers() {
+ // get all layers of the layer group
+ let layers = this._layerGroup.getLayers();
+ // filter out layers that don't have leaflet-geoman
+ layers = layers.filter(layer => !!layer.pm);
+ // filter out everything that's leaflet-geoman specific temporary stuff
+ layers = layers.filter(layer => !layer._pmTempLayer);
+ // return them
+ return layers;
},
dragging() {
- const dragging = this._layers.find(layer => layer.pm.dragging());
- return !!dragging;
+ if (this._layers) {
+ const dragging = this._layers.find(layer => layer.pm.dragging());
+ return !!dragging;
+ }
+ return false;
},
getOptions() {
return this._options;
diff --git a/src/js/Edit/L.PM.Edit.Line.js b/src/js/Edit/L.PM.Edit.Line.js
index 1dd2a8bb..551dd04e 100644
--- a/src/js/Edit/L.PM.Edit.Line.js
+++ b/src/js/Edit/L.PM.Edit.Line.js
@@ -3,7 +3,7 @@ import lineIntersect from '@turf/line-intersect';
import get from 'lodash/get';
import Edit from './L.PM.Edit';
import Utils from '../L.PM.Utils';
-import { isEmptyDeep } from '../helpers';
+import { isEmptyDeep, removeEmptyCoordRings } from '../helpers';
import MarkerLimits from '../Mixins/MarkerLimits';
@@ -22,25 +22,6 @@ Edit.Line = Edit.extend({
this._layer = layer;
this._enabled = false;
},
-
- applyOptions() {
- if (this.options.snappable) {
- this._initSnappableMarkers();
- } else {
- this._disableSnapping();
- }
- },
-
- toggleEdit(options) {
- if (!this.enabled()) {
- this.enable(options);
- } else {
- this.disable();
- }
-
- return this.enabled();
- },
-
enable(options) {
L.Util.setOptions(this, options);
@@ -51,6 +32,7 @@ Edit.Line = Edit.extend({
return;
}
+ // TODO: this is wrong: if already enable then go into the if
if (!this.enabled()) {
// if it was already enabled, disable first
// we don't block enabling again because new options might be passed
@@ -65,8 +47,6 @@ Edit.Line = Edit.extend({
this.applyOptions();
- this._layer.fire('pm:enable', { layer: this._layer, shape: this.getShape() });
-
// if polygon gets removed from map, disable edit mode
this._layer.on('remove', this._onLayerRemove, this);
@@ -78,23 +58,19 @@ Edit.Line = Edit.extend({
);
}
- this.cachedColor = undefined;
if (!this.options.allowSelfIntersection) {
- this.cachedColor = this._layer.options.color;
-
- this.isRed = false;
+ if(this._layer.options.color !== 'red') {
+ this.cachedColor = this._layer.options.color;
+ this.isRed = false;
+ }else{
+ this.isRed = true;
+ }
this._handleLayerStyle();
+ }else{
+ this.cachedColor = undefined;
}
+ Utils._fireEvent(this._layer,'pm:enable', { layer: this._layer, shape: this.getShape() });
},
-
- _onLayerRemove(e) {
- this.disable(e.target);
- },
-
- enabled() {
- return this._enabled;
- },
-
disable(poly = this._layer) {
// if it's not enabled, it doesn't need to be disabled
if (!this.enabled()) {
@@ -116,8 +92,6 @@ Edit.Line = Edit.extend({
// remove onRemove listener
this._layer.off('remove', this._onLayerRemove, this);
-
-
if (!this.options.allowSelfIntersection) {
this._layer.off(
'pm:vertexremoved',
@@ -135,93 +109,34 @@ Edit.Line = Edit.extend({
L.DomUtil.removeClass(el, 'leaflet-pm-invalid');
}
- this._layer.fire('pm:disable', { layer: this._layer, shape: this.getShape() });
-
if (this._layerEdited) {
- this._layer.fire('pm:update', { layer: this._layer, shape: this.getShape() });
+ Utils._fireEvent(this._layer,'pm:update', { layer: this._layer, shape: this.getShape() });
}
this._layerEdited = false;
-
+ Utils._fireEvent(this._layer,'pm:disable', { layer: this._layer, shape: this.getShape() });
return true;
},
-
- hasSelfIntersection() {
- // check for self intersection of the layer and return true/false
- const selfIntersection = kinks(this._layer.toGeoJSON(15));
- return selfIntersection.features.length > 0;
+ enabled() {
+ return this._enabled;
},
-
- _handleSelfIntersectionOnVertexRemoval() {
- // check for selfintersection again (mainly to reset the style)
- this._handleLayerStyle(true);
-
- if (this.hasSelfIntersection()) {
- // reset coordinates
- this._layer.setLatLngs(this._coordsBeforeEdit);
- this._coordsBeforeEdit = null;
-
- // re-enable markers for the new coords
- this._initMarkers();
+ toggleEdit(options) {
+ if (!this.enabled()) {
+ this.enable(options);
+ } else {
+ this.disable();
}
+ return this.enabled();
},
-
- _handleLayerStyle(flash) {
- const layer = this._layer;
-
- if (this.hasSelfIntersection()) {
- if (!this.options.allowSelfIntersection && this.options.allowSelfIntersectionEdit) {
- this._updateDisabledMarkerStyle(this._markers, true);
- }
-
- if (this.isRed) {
- return;
- }
-
- // if it does self-intersect, mark or flash it red
- if (flash) {
- layer.setStyle({ color: 'red' });
- this.isRed = true;
-
- window.setTimeout(() => {
- layer.setStyle({ color: this.cachedColor });
- this.isRed = false;
- }, 200);
- } else {
- layer.setStyle({ color: 'red' });
- this.isRed = true;
- }
-
- // fire intersect event
- this._layer.fire('pm:intersect', {
- layer: this._layer,
- intersection: kinks(this._layer.toGeoJSON(15)),
- shape: this.getShape()
- });
+ applyOptions() {
+ if (this.options.snappable) {
+ this._initSnappableMarkers();
} else {
- // if not, reset the style to the default color
- layer.setStyle({ color: this.cachedColor });
- this.isRed = false;
- if (!this.options.allowSelfIntersection && this.options.allowSelfIntersectionEdit) {
- this._updateDisabledMarkerStyle(this._markers, false);
- }
+ this._disableSnapping();
}
},
- _updateDisabledMarkerStyle(markers, disabled) {
- markers.forEach((marker) => {
- if (Array.isArray(marker)) {
- return this._updateDisabledMarkerStyle(marker, disabled);
- }
-
- if (marker._icon) {
- if (disabled && !this._checkMarkerAllowedToDrag(marker)) {
- L.DomUtil.addClass(marker._icon, "vertexmarker-disabled");
- } else {
- L.DomUtil.removeClass(marker._icon, "vertexmarker-disabled");
- }
- }
- });
+ _onLayerRemove(e) {
+ this.disable(e.target);
},
-
_initMarkers() {
const map = this._map;
const coords = this._layer.getLatLngs();
@@ -382,7 +297,7 @@ Edit.Line = Edit.extend({
// fire edit event
this._fireEdit();
- this._layer.fire('pm:vertexadded', {
+ Utils._fireEvent(this._layer,'pm:vertexadded', {
layer: this._layer,
marker: newM,
indexPath: this.findDeepMarkerIndex(this._markers, newM).indexPath,
@@ -395,6 +310,83 @@ Edit.Line = Edit.extend({
}
},
+ hasSelfIntersection() {
+ // check for self intersection of the layer and return true/false
+ const selfIntersection = kinks(this._layer.toGeoJSON(15));
+ return selfIntersection.features.length > 0;
+ },
+
+ _handleSelfIntersectionOnVertexRemoval() {
+ // check for selfintersection again (mainly to reset the style)
+ this._handleLayerStyle(true);
+
+ if (this.hasSelfIntersection()) {
+ // reset coordinates
+ this._layer.setLatLngs(this._coordsBeforeEdit);
+ this._coordsBeforeEdit = null;
+
+ // re-enable markers for the new coords
+ this._initMarkers();
+ }
+ },
+
+ _handleLayerStyle(flash) {
+ const layer = this._layer;
+
+ if (this.hasSelfIntersection()) {
+ if (!this.options.allowSelfIntersection && this.options.allowSelfIntersectionEdit) {
+ this._updateDisabledMarkerStyle(this._markers, true);
+ }
+
+ if (this.isRed) {
+ return;
+ }
+
+ // if it does self-intersect, mark or flash it red
+ if (flash) {
+ layer.setStyle({ color: 'red' });
+ this.isRed = true;
+
+ window.setTimeout(() => {
+ layer.setStyle({ color: this.cachedColor });
+ this.isRed = false;
+ }, 200);
+ } else {
+ layer.setStyle({ color: 'red' });
+ this.isRed = true;
+ }
+
+ // fire intersect event
+ Utils._fireEvent(this._layer,'pm:intersect', {
+ layer: this._layer,
+ intersection: kinks(this._layer.toGeoJSON(15)),
+ shape: this.getShape()
+ });
+ } else {
+ // if not, reset the style to the default color
+ layer.setStyle({ color: this.cachedColor });
+ this.isRed = false;
+ if (!this.options.allowSelfIntersection && this.options.allowSelfIntersectionEdit) {
+ this._updateDisabledMarkerStyle(this._markers, false);
+ }
+ }
+ },
+ _updateDisabledMarkerStyle(markers, disabled) {
+ markers.forEach((marker) => {
+ if (Array.isArray(marker)) {
+ return this._updateDisabledMarkerStyle(marker, disabled);
+ }
+
+ if (marker._icon) {
+ if (disabled && !this._checkMarkerAllowedToDrag(marker)) {
+ L.DomUtil.addClass(marker._icon, "vertexmarker-disabled");
+ } else {
+ L.DomUtil.removeClass(marker._icon, "vertexmarker-disabled");
+ }
+ }
+ });
+ },
+
_removeMarker(e) {
// if self intersection isn't allowed, save the coords upon dragstart
// in case we need to reset the layer
@@ -407,7 +399,7 @@ Edit.Line = Edit.extend({
const marker = e.target;
// coords of the layer
- const coords = this._layer.getLatLngs();
+ let coords = this._layer.getLatLngs();
// the index path to the marker inside the multidimensional marker array
const { indexPath, index, parentPath } = this.findDeepMarkerIndex(
@@ -424,7 +416,7 @@ Edit.Line = Edit.extend({
const coordsRing = indexPath.length > 1 ? get(coords, parentPath) : coords;
// define the markers array that is edited
- const markerArr =
+ let markerArr =
indexPath.length > 1 ? get(this._markers, parentPath) : this._markers;
// remove coordinate
@@ -449,15 +441,22 @@ Edit.Line = Edit.extend({
// TODO: kind of an ugly workaround maybe do it better?
this.disable();
this.enable(this.options);
- }
- // TODO: we may should remove all empty coord-rings here as well.
+ }
// if no coords are left, remove the layer
if (isEmptyDeep(coords)) {
this._layer.remove();
}
+ // remove all empty coord-rings
+ coords = removeEmptyCoordRings(coords);
+ this._layer.setLatLngs(coords);
+ // remove empty marker arrays
+ this._markers = removeEmptyCoordRings(this._markers);
+ // get new markerArr because we cleaned up coords and markers array
+ markerArr = indexPath.length > 1 ? get(this._markers, parentPath) : this._markers;
+
// now handle the middle markers
// remove the marker and the middlemarkers next to it from the map
if (marker._middleMarkerPrev) {
@@ -470,36 +469,38 @@ Edit.Line = Edit.extend({
// remove the marker from the map
this._markerGroup.removeLayer(marker);
- let rightMarkerIndex;
- let leftMarkerIndex;
+ if(markerArr) {
+ let rightMarkerIndex;
+ let leftMarkerIndex;
- if (this.isPolygon()) {
- // find neighbor marker-indexes
- rightMarkerIndex = (index + 1) % markerArr.length;
- leftMarkerIndex = (index + (markerArr.length - 1)) % markerArr.length;
- } else {
- // find neighbor marker-indexes
- leftMarkerIndex = index - 1 < 0 ? undefined : index - 1;
- rightMarkerIndex = index + 1 >= markerArr.length ? undefined : index + 1;
- }
+ if (this.isPolygon()) {
+ // find neighbor marker-indexes
+ rightMarkerIndex = (index + 1) % markerArr.length;
+ leftMarkerIndex = (index + (markerArr.length - 1)) % markerArr.length;
+ } else {
+ // find neighbor marker-indexes
+ leftMarkerIndex = index - 1 < 0 ? undefined : index - 1;
+ rightMarkerIndex = index + 1 >= markerArr.length ? undefined : index + 1;
+ }
- // don't create middlemarkers if there is only one marker left
- if (rightMarkerIndex !== leftMarkerIndex) {
- const leftM = markerArr[leftMarkerIndex];
- const rightM = markerArr[rightMarkerIndex];
- if (this.options.hideMiddleMarkers !== true) {
- this._createMiddleMarker(leftM, rightM);
+ // don't create middlemarkers if there is only one marker left
+ if (rightMarkerIndex !== leftMarkerIndex) {
+ const leftM = markerArr[leftMarkerIndex];
+ const rightM = markerArr[rightMarkerIndex];
+ if (this.options.hideMiddleMarkers !== true) {
+ this._createMiddleMarker(leftM, rightM);
+ }
}
- }
- // remove the marker from the markers array
- markerArr.splice(index, 1);
+ // remove the marker from the markers array
+ markerArr.splice(index, 1);
+ }
// fire edit event
this._fireEdit();
// fire vertex removal event
- this._layer.fire('pm:vertexremoved', {
+ Utils._fireEvent(this._layer,'pm:vertexremoved', {
layer: this._layer,
marker,
indexPath,
@@ -576,6 +577,58 @@ Edit.Line = Edit.extend({
return { prevMarker, nextMarker };
},
+ _checkMarkerAllowedToDrag(marker) {
+ const { prevMarker, nextMarker } = this._getNeighborMarkers(marker);
+
+ const prevLine = L.polyline([prevMarker.getLatLng(), marker.getLatLng()]);
+ const nextLine = L.polyline([marker.getLatLng(), nextMarker.getLatLng()]);
+
+ let prevLineIntersectionLen = lineIntersect(this._layer.toGeoJSON(15), prevLine.toGeoJSON(15)).features.length;
+ let nextLineIntersectionLen = lineIntersect(this._layer.toGeoJSON(15), nextLine.toGeoJSON(15)).features.length;
+
+ // The first and last line has one intersection fewer because they are not connected
+ if (marker.getLatLng() === this._markers[0][0].getLatLng()) {
+ nextLineIntersectionLen += 1;
+ } else if (marker.getLatLng() === this._markers[0][this._markers[0].length - 1].getLatLng()) {
+ prevLineIntersectionLen += 1;
+ }
+
+ // <= 2 the start and end point of the line always intersecting because they have the same coords.
+ if (prevLineIntersectionLen <= 2 && nextLineIntersectionLen <= 2) {
+ return false;
+ }
+ return true;
+
+ },
+ _onMarkerDragStart(e) {
+ const marker = e.target;
+ const { indexPath } = this.findDeepMarkerIndex(this._markers, marker);
+
+ Utils._fireEvent(this._layer,'pm:markerdragstart', {
+ layer: this._layer,
+ markerEvent: e,
+ indexPath,
+ shape: this.getShape()
+ });
+
+ // if self intersection isn't allowed, save the coords upon dragstart
+ // in case we need to reset the layer
+ if (!this.options.allowSelfIntersection) {
+ this._coordsBeforeEdit = this._layer.getLatLngs();
+ }
+
+ // When intersection is true while calling enable(), the cachedColor is already set
+ if (!this.cachedColor) {
+ this.cachedColor = this._layer.options.color;
+ }
+
+
+ if (!this.options.allowSelfIntersection && this.options.allowSelfIntersectionEdit && this.hasSelfIntersection()) {
+ this._markerAllowedToDrag = this._checkMarkerAllowedToDrag(marker);
+ } else {
+ this._markerAllowedToDrag = null;
+ }
+ },
_onMarkerDrag(e) {
// dragged marker
const marker = e.target;
@@ -639,13 +692,18 @@ Edit.Line = Edit.extend({
if (!this.options.allowSelfIntersection) {
this._handleLayerStyle();
}
+ Utils._fireEvent(this._layer,'pm:markerdrag', {
+ layer: this._layer,
+ markerEvent: e,
+ shape: this.getShape(),
+ indexPath
+ });
},
-
_onMarkerDragEnd(e) {
const marker = e.target;
const { indexPath } = this.findDeepMarkerIndex(this._markers, marker);
- this._layer.fire('pm:markerdragend', {
+ Utils._fireEvent(this._layer,'pm:markerdragend', {
layer: this._layer,
markerEvent: e,
indexPath,
@@ -674,66 +732,12 @@ Edit.Line = Edit.extend({
if (!this.options.allowSelfIntersection && this.options.allowSelfIntersectionEdit) {
this._handleLayerStyle();
}
-
-
// fire edit event
this._fireEdit();
},
- _onMarkerDragStart(e) {
- const marker = e.target;
- const { indexPath } = this.findDeepMarkerIndex(this._markers, marker);
-
- this._layer.fire('pm:markerdragstart', {
- layer: this._layer,
- markerEvent: e,
- indexPath,
- shape: this.getShape()
- });
-
- // if self intersection isn't allowed, save the coords upon dragstart
- // in case we need to reset the layer
- if (!this.options.allowSelfIntersection) {
- this._coordsBeforeEdit = this._layer.getLatLngs();
- }
-
- // When intersection is true while calling enable(), the cachedColor is already set
- if (!this.cachedColor) {
- this.cachedColor = this._layer.options.color;
- }
-
-
- if (!this.options.allowSelfIntersection && this.options.allowSelfIntersectionEdit && this.hasSelfIntersection()) {
- this._markerAllowedToDrag = this._checkMarkerAllowedToDrag(marker);
- } else {
- this._markerAllowedToDrag = null;
- }
- },
- _checkMarkerAllowedToDrag(marker) {
- const { prevMarker, nextMarker } = this._getNeighborMarkers(marker);
-
- const prevLine = L.polyline([prevMarker.getLatLng(), marker.getLatLng()]);
- const nextLine = L.polyline([marker.getLatLng(), nextMarker.getLatLng()]);
-
- let prevLineIntersectionLen = lineIntersect(this._layer.toGeoJSON(15), prevLine.toGeoJSON(15)).features.length;
- let nextLineIntersectionLen = lineIntersect(this._layer.toGeoJSON(15), nextLine.toGeoJSON(15)).features.length;
-
- // The first and last line has one intersection fewer because they are not connected
- if (marker.getLatLng() === this._markers[0][0].getLatLng()) {
- nextLineIntersectionLen += 1;
- } else if (marker.getLatLng() === this._markers[0][this._markers[0].length - 1].getLatLng()) {
- prevLineIntersectionLen += 1;
- }
-
- // <= 2 the start and end point of the line always intersecting because they have the same coords.
- if (prevLineIntersectionLen <= 2 && nextLineIntersectionLen <= 2) {
- return false;
- }
- return true;
-
- },
_fireEdit() {
// fire edit event
this._layerEdited = true;
- this._layer.fire('pm:edit', { layer: this._layer, shape: this.getShape() });
+ Utils._fireEvent(this._layer,'pm:edit', { layer: this._layer, shape: this.getShape() });
},
});
diff --git a/src/js/Edit/L.PM.Edit.Marker.js b/src/js/Edit/L.PM.Edit.Marker.js
index 9d01d971..e35a5c70 100644
--- a/src/js/Edit/L.PM.Edit.Marker.js
+++ b/src/js/Edit/L.PM.Edit.Marker.js
@@ -1,4 +1,5 @@
import Edit from './L.PM.Edit';
+import Utils from "../L.PM.Utils";
Edit.Marker = Edit.extend({
_shape: 'Marker',
@@ -10,35 +11,6 @@ Edit.Marker = Edit.extend({
// register dragend event e.g. to fire pm:edit
this._layer.on('dragend', this._onDragEnd, this);
},
-
- applyOptions() {
- // console.log('apply options', this.options)
-
- if (this.options.snappable) {
- this._initSnappableMarkers();
- } else {
- this._disableSnapping();
- }
-
- if (this.options.draggable) {
- this.enableLayerDrag();
- } else {
- this.disableLayerDrag();
- }
- // enable removal for the marker
- if (!this.options.preventMarkerRemoval) {
- this._layer.on('contextmenu', this._removeMarker, this);
- }
- },
-
- toggleEdit(options) {
- if (!this.enabled()) {
- this.enable(options);
- } else {
- this.disable();
- }
- },
-
enable(options = { draggable: true }) {
L.Util.setOptions(this, options);
@@ -47,17 +19,11 @@ Edit.Marker = Edit.extend({
if (this.enabled()) {
return;
}
- this._enabled = true;
-
- this._layer.fire('pm:enable', { layer: this._layer, shape: this.getShape() });
-
this.applyOptions();
- },
+ this._enabled = true;
- enabled() {
- return this._enabled;
+ Utils._fireEvent(this._layer,'pm:enable', { layer: this._layer, shape: this.getShape() });
},
-
disable() {
this._enabled = false;
@@ -66,25 +32,52 @@ Edit.Marker = Edit.extend({
this._layer.off('contextmenu', this._removeMarker, this);
- this._layer.fire('pm:disable', { layer: this._layer, shape: this.getShape() });
+ Utils._fireEvent(this._layer,'pm:disable', { layer: this._layer, shape: this.getShape() });
if (this._layerEdited) {
- this._layer.fire('pm:update', { layer: this._layer, shape: this.getShape() });
+ Utils._fireEvent(this._layer,'pm:update', { layer: this._layer, shape: this.getShape() });
}
this._layerEdited = false;
},
+ enabled() {
+ return this._enabled;
+ },
+ toggleEdit(options) {
+ if (!this.enabled()) {
+ this.enable(options);
+ } else {
+ this.disable();
+ }
+ },
+ applyOptions() {
+ if (this.options.snappable) {
+ this._initSnappableMarkers();
+ } else {
+ this._disableSnapping();
+ }
+
+ if (this.options.draggable) {
+ this.enableLayerDrag();
+ } else {
+ this.disableLayerDrag();
+ }
+ // enable removal for the marker
+ if (!this.options.preventMarkerRemoval) {
+ this._layer.on('contextmenu', this._removeMarker, this);
+ }
+ },
_removeMarker(e) {
const marker = e.target;
marker.remove();
// TODO: find out why this is fired manually, shouldn't it be catched by L.PM.Map 'layerremove'?
- marker.fire('pm:remove', { layer: marker, shape: this.getShape() });
- this._map.fire('pm:remove', { layer: marker, shape: this.getShape() });
+ Utils._fireEvent(marker,'pm:remove', { layer: marker, shape: this.getShape() });
+ Utils._fireEvent(this._map,'pm:remove', { layer: marker, shape: this.getShape() });
},
_onDragEnd(e) {
const marker = e.target;
// fire the pm:edit event and pass shape and marker
- marker.fire('pm:edit', { layer: this._layer, shape: this.getShape() });
+ Utils._fireEvent(marker,'pm:edit', { layer: this._layer, shape: this.getShape() });
this._layerEdited = true;
},
// overwrite initSnappableMarkers from Snapping.js Mixin
diff --git a/src/js/Edit/L.PM.Edit.Rectangle.js b/src/js/Edit/L.PM.Edit.Rectangle.js
index 8f1c7d81..dbd984e1 100644
--- a/src/js/Edit/L.PM.Edit.Rectangle.js
+++ b/src/js/Edit/L.PM.Edit.Rectangle.js
@@ -2,6 +2,7 @@
// https://github.com/Leaflet/Leaflet.draw/blob/master/src/edit/handler/Edit.Rectangle.js
import Edit from './L.PM.Edit';
+import Utils from "../L.PM.Utils";
Edit.Rectangle = Edit.Polygon.extend({
_shape: 'Rectangle',
@@ -28,10 +29,14 @@ Edit.Rectangle = Edit.Polygon.extend({
// convenience alias, for better readability
[this._cornerMarkers] = this._markers;
-
+ },
+ applyOptions() {
if (this.options.snappable) {
this._initSnappableMarkers();
+ } else {
+ this._disableSnapping();
}
+ this._addMarkerEvents();
},
// creates initial markers for coordinates
@@ -45,18 +50,22 @@ Edit.Rectangle = Edit.Polygon.extend({
marker._index = index;
marker._pmTempLayer = true;
- marker.on('dragstart', this._onMarkerDragStart, this);
- marker.on('drag', this._onMarkerDrag, this);
- marker.on('dragend', this._onMarkerDragEnd, this);
- marker.on('pm:snap', this._adjustRectangleForMarkerSnap, this);
- if (!this.options.preventMarkerRemoval) {
- marker.on('contextmenu', this._removeMarker, this);
- }
this._markerGroup.addLayer(marker);
return marker;
},
-
+ // Add marker events after adding the snapping events to the markers, beacause of the execution order
+ _addMarkerEvents(){
+ this._markers[0].forEach((marker)=>{
+ marker.on('dragstart', this._onMarkerDragStart, this);
+ marker.on('drag', this._onMarkerDrag, this);
+ marker.on('dragend', this._onMarkerDragEnd, this);
+ marker.on('pm:snap', this._adjustRectangleForMarkerSnap, this);
+ if (!this.options.preventMarkerRemoval) {
+ marker.on('contextmenu', this._removeMarker, this);
+ }
+ });
+ },
// Empty callback for 'contextmenu' binding set in L.PM.Edit.Line.js's _createMarker method (AKA, right-click on marker event)
// (A Rectangle is designed to always remain a "true" rectangle -- if you want it editable, use Polygon Tool instead!!!)
_removeMarker() {
@@ -77,7 +86,7 @@ Edit.Rectangle = Edit.Polygon.extend({
// (Without this, it's occasionally possible for a marker to get stuck as 'snapped,' which prevents Rectangle resizing)
draggedMarker._snapped = false;
- this._layer.fire('pm:markerdragstart', {
+ Utils._fireEvent(this._layer,'pm:markerdragstart', {
layer: this._layer,
markerEvent: e,
shape: this.getShape(),
@@ -98,6 +107,13 @@ Edit.Rectangle = Edit.Polygon.extend({
if (!draggedMarker._snapped) {
this._adjustRectangleForMarkerMove(draggedMarker);
}
+
+ Utils._fireEvent(this._layer,'pm:markerdrag', {
+ layer: this._layer,
+ markerEvent: e,
+ shape: this.getShape(),
+ indexPath: undefined
+ });
},
_onMarkerDragEnd(e) {
@@ -114,7 +130,7 @@ Edit.Rectangle = Edit.Polygon.extend({
// Update bounding box
this._layer.setLatLngs(corners);
- this._layer.fire('pm:markerdragend', {
+ Utils._fireEvent(this._layer,'pm:markerdragend', {
layer: this._layer,
markerEvent: e,
shape: this.getShape(),
diff --git a/src/js/Edit/L.PM.Edit.js b/src/js/Edit/L.PM.Edit.js
index 90869ea3..98fd3dd1 100644
--- a/src/js/Edit/L.PM.Edit.js
+++ b/src/js/Edit/L.PM.Edit.js
@@ -23,7 +23,7 @@ const Edit = L.Class.extend({
},
getShape(){
return this._shape;
- }
+ },
});
export default Edit;
diff --git a/src/js/L.PM.Map.js b/src/js/L.PM.Map.js
index 1933f61e..8acdec19 100644
--- a/src/js/L.PM.Map.js
+++ b/src/js/L.PM.Map.js
@@ -20,6 +20,8 @@ const Map = L.Class.extend({
this.globalOptions = {
snappable: true,
+ layerGroup: undefined,
+ snappingOrder: ['Marker','CircleMarker','Circle','Line','Polygon','Rectangle']
};
},
setLang(lang = 'en', t, fallback = 'en') {
@@ -30,7 +32,7 @@ const Map = L.Class.extend({
L.PM.activeLang = lang;
this.map.pm.Toolbar.reinit();
- this.map.fire("pm:langchange", {
+ Utils._fireEvent(this.map,"pm:langchange", {
oldLang,
activeLang: lang,
fallback,
@@ -86,10 +88,21 @@ const Map = L.Class.extend({
...o
};
+ // check if switched the editable mode for CircleMarker while drawing
+ let reenableCircleMarker = false;
+ if(this.map.pm.Draw.CircleMarker.enabled() && this.map.pm.Draw.CircleMarker.options.editable !== options.editable){
+ this.map.pm.Draw.CircleMarker.disable();
+ reenableCircleMarker = true;
+ }
+
// enable options for Drawing Shapes
this.map.pm.Draw.shapes.forEach(shape => {
this.map.pm.Draw[shape].setOptions(options)
- })
+ });
+
+ if(reenableCircleMarker){
+ this.map.pm.Draw.CircleMarker.enable();
+ }
// enable options for Editing
const layers = findLayers(this.map);
@@ -126,11 +139,33 @@ const Map = L.Class.extend({
disableGlobalCutMode() {
return this.Draw.Cut.disable();
},
- getGeomanLayers(){
- return findLayers(this.map);
+ getGeomanLayers(asGroup = false){
+ const layers = findLayers(this.map);
+ if(!asGroup) {
+ return layers;
+ }
+ const group = L.featureGroup();
+ group._pmTempLayer = true;
+ layers.forEach((layer) => {
+ group.addLayer(layer);
+ });
+ return group;
+ },
+ getGeomanDrawLayers(asGroup = false){
+ const layers = findLayers(this.map).filter(l => l._drawnByGeoman === true);
+ if(!asGroup) {
+ return layers;
+ }
+ const group = L.featureGroup();
+ group._pmTempLayer = true;
+ layers.forEach((layer) => {
+ group.addLayer(layer);
+ });
+ return group;
},
- getGeomanDrawLayers(){
- return findLayers(this.map).filter(l => l._drawnByGeoman === true);
+ // returns the map instance by default or a layergroup is set through global options
+ _getContainingLayer(){
+ return this.globalOptions.layerGroup && this.globalOptions.layerGroup instanceof L.LayerGroup ? this.globalOptions.layerGroup : this.map;
}
});
diff --git a/src/js/L.PM.Utils.js b/src/js/L.PM.Utils.js
index 33eac2b3..663dda01 100644
--- a/src/js/L.PM.Utils.js
+++ b/src/js/L.PM.Utils.js
@@ -16,7 +16,8 @@ const Utils = {
layer instanceof L.Polyline ||
layer instanceof L.Marker ||
layer instanceof L.Circle ||
- layer instanceof L.CircleMarker
+ layer instanceof L.CircleMarker ||
+ layer instanceof L.ImageOverlay
) {
layers.push(layer);
}
@@ -41,8 +42,111 @@ const Utils = {
}
return L.polygon(polygon, circle.options);
},
+ disablePopup(layer){
+ if(layer.getPopup()){
+ layer._tempPopupCopy = layer.getPopup();
+ layer.unbindPopup();
+ }
+ },
+ enablePopup(layer){
+ if(layer._tempPopupCopy){
+ layer.bindPopup(layer._tempPopupCopy);
+ delete layer._tempPopupCopy;
+ }
+ },
+ _fireEvent(layer,type,data,propagate = false) {
+ layer.fire(type, data, propagate);
+
+ // fire event to all parent layers
+ const {groups} = this.getAllParentGroups(layer);
+ groups.forEach((group) => {
+ group.fire(type, data, propagate);
+ });
+ },
+ getAllParentGroups(layer){
+ const groupIds = [];
+ const groups = [];
+
+ // get every group layer once
+ const loopThroughParents = (_layer) => {
+ for (const _id in _layer._eventParents) {
+ if(groupIds.indexOf(_id) === -1){
+ groupIds.push(_id);
+ const group = _layer._eventParents[_id];
+ groups.push(group);
+ loopThroughParents(group)
+ }
+ }
+ };
+
+ // check if the last group fetch is under 1 sec, then we use the groups from before
+ if(!layer._pmLastGroupFetch || !layer._pmLastGroupFetch.time || (new Date().getTime() - layer._pmLastGroupFetch.time)> 1000){
+ loopThroughParents(layer);
+ layer._pmLastGroupFetch = {
+ time: new Date().getTime(),
+ groups,
+ groupIds
+ } ;
+ return {
+ groupIds,
+ groups
+ }
+ }else{
+ return {
+ groups: layer._pmLastGroupFetch.groups,
+ groupIds: layer._pmLastGroupFetch.groupIds
+ }
+ }
+ },
createGeodesicPolygon,
getTranslation,
+ findDeepCoordIndex(arr, latlng) {
+ // find latlng in arr and return its location as path
+ // thanks for the function, Felix Heck
+ let result;
+
+ const run = path => (v, i) => {
+ const iRes = path.concat(i);
+
+ if (v.lat && v.lat === latlng.lat && v.lng === latlng.lng) {
+ result = iRes;
+ return true;
+ }
+
+ return Array.isArray(v) && v.some(run(iRes));
+ };
+ arr.some(run([]));
+
+ let returnVal = {};
+
+ if (result) {
+ returnVal = {
+ indexPath: result,
+ index: result[result.length - 1],
+ parentPath: result.slice(0, result.length - 1),
+ };
+ }
+
+ return returnVal;
+ },
+ _getIndexFromSegment(coords, segment) {
+ if (segment && segment.length === 2) {
+ const indexA = this.findDeepCoordIndex(coords, segment[0]);
+ const indexB = this.findDeepCoordIndex(coords, segment[1]);
+ let newIndex = Math.max(indexA.index, indexB.index);
+ if ((indexA.index === 0 || indexB.index === 0) && newIndex !== 1) {
+ newIndex+=1;
+ }
+ return {
+ indexA,
+ indexB,
+ newIndex,
+ indexPath: indexA.indexPath,
+ parentPath: indexA.parentPath,
+ };
+ }
+ return null;
+ },
};
export default Utils;
diff --git a/src/js/L.PM.js b/src/js/L.PM.js
index a4c45cb6..f94bb446 100644
--- a/src/js/L.PM.js
+++ b/src/js/L.PM.js
@@ -31,6 +31,7 @@ import './Edit/L.PM.Edit.Polygon';
import './Edit/L.PM.Edit.Rectangle';
import './Edit/L.PM.Edit.Circle';
import './Edit/L.PM.Edit.CircleMarker';
+import './Edit/L.PM.Edit.ImageOverlay';
import '../css/layers.css';
import '../css/controls.css';
@@ -45,15 +46,19 @@ L.PM = L.PM || {
Edit,
Utils,
activeLang: 'en',
+ optIn: false,
initialize(options) {
this.addInitHooks(options);
},
+ setOptIn(value){
+ this.optIn = !!value;
+ },
addInitHooks(options = {}) {
function initMap() {
this.pm = undefined;
- if (options.optIn) {
+ if (L.PM.optIn) {
if (this.options.pmIgnore === false) {
this.pm = new L.PM.Map(this);
}
@@ -65,8 +70,14 @@ L.PM = L.PM || {
L.Map.addInitHook(initMap);
function initLayerGroup() {
- // doesn't need pmIgnore condition as the init hook of the individual layers will check it
- this.pm = new L.PM.Edit.LayerGroup(this);
+ this.pm = undefined;
+ if (L.PM.optIn) {
+ if (this.options.pmIgnore === false) {
+ this.pm = new L.PM.Edit.LayerGroup(this);
+ }
+ } else if (!this.options.pmIgnore) {
+ this.pm = new L.PM.Edit.LayerGroup(this);
+ }
}
L.LayerGroup.addInitHook(initLayerGroup);
@@ -74,7 +85,7 @@ L.PM = L.PM || {
function initMarker() {
this.pm = undefined;
- if (options.optIn) {
+ if (L.PM.optIn) {
if (this.options.pmIgnore === false) {
this.pm = new L.PM.Edit.Marker(this);
}
@@ -82,13 +93,12 @@ L.PM = L.PM || {
this.pm = new L.PM.Edit.Marker(this);
}
}
-
L.Marker.addInitHook(initMarker);
function initCircleMarker() {
this.pm = undefined;
- if (options.optIn) {
+ if (L.PM.optIn) {
if (this.options.pmIgnore === false) {
this.pm = new L.PM.Edit.CircleMarker(this);
}
@@ -102,7 +112,7 @@ L.PM = L.PM || {
function initPolyline() {
this.pm = undefined;
- if (options.optIn) {
+ if (L.PM.optIn) {
if (this.options.pmIgnore === false) {
this.pm = new L.PM.Edit.Line(this);
}
@@ -116,7 +126,7 @@ L.PM = L.PM || {
function initPolygon() {
this.pm = undefined;
- if (options.optIn) {
+ if (L.PM.optIn) {
if (this.options.pmIgnore === false) {
this.pm = new L.PM.Edit.Polygon(this);
}
@@ -131,7 +141,7 @@ L.PM = L.PM || {
function initRectangle() {
this.pm = undefined;
- if (options.optIn) {
+ if (L.PM.optIn) {
if (this.options.pmIgnore === false) {
this.pm = new L.PM.Edit.Rectangle(this);
}
@@ -145,7 +155,7 @@ L.PM = L.PM || {
function initCircle() {
this.pm = undefined;
- if (options.optIn) {
+ if (L.PM.optIn) {
if (this.options.pmIgnore === false) {
this.pm = new L.PM.Edit.Circle(this);
}
@@ -155,7 +165,49 @@ L.PM = L.PM || {
}
L.Circle.addInitHook(initCircle);
+
+
+ function initImageOverlay() {
+ this.pm = undefined;
+
+ if (L.PM.optIn) {
+ if (this.options.pmIgnore === false) {
+ this.pm = new L.PM.Edit.ImageOverlay(this);
+ }
+ } else if (!this.options.pmIgnore) {
+ this.pm = new L.PM.Edit.ImageOverlay(this);
+ }
+ }
+
+ L.ImageOverlay.addInitHook(initImageOverlay);
},
+ reInitLayer(layer){
+ if(layer instanceof L.LayerGroup){
+ layer.eachLayer((_layer)=>{
+ this.reInitLayer(_layer);
+ })
+ }else if(layer.pm){
+ // PM is already added to the layer
+ }else if(L.PM.optIn && layer.options.pmIgnore !== false){
+ // Opt-In is true and pmIgnore is not false
+ }else if(layer.options.pmIgnore){
+ // pmIgnore is true
+ }else if(layer instanceof L.Map){
+ layer.pm = new L.PM.Map(layer);
+ }else if(layer instanceof L.Marker){
+ layer.pm = new L.PM.Edit.Marker(layer);
+ }else if(layer instanceof L.Circle){
+ layer.pm = new L.PM.Edit.Circle(layer);
+ }else if(layer instanceof L.CircleMarker){
+ layer.pm = new L.PM.Edit.CircleMarker(layer);
+ }else if(layer instanceof L.Rectangle){
+ layer.pm = new L.PM.Edit.Rectangle(layer);
+ }else if(layer instanceof L.Polygon){
+ layer.pm = new L.PM.Edit.Polygon(layer);
+ }else if(layer instanceof L.Polyline){
+ layer.pm = new L.PM.Edit.Line(layer);
+ }
+ }
};
// initialize leaflet-geoman
diff --git a/src/js/Mixins/Dragging.js b/src/js/Mixins/Dragging.js
index 56e9ce57..9063bd01 100644
--- a/src/js/Mixins/Dragging.js
+++ b/src/js/Mixins/Dragging.js
@@ -1,3 +1,5 @@
+import Utils from "../L.PM.Utils";
+
const DragMixin = {
enableLayerDrag() {
// before enabling layer drag, disable layer editing
@@ -22,6 +24,8 @@ const DragMixin = {
this._layer.dragging.enable();
}
return;
+ }else if(this._layer instanceof L.ImageOverlay){
+ this._getDOMElem().ondragstart = ()=>false;
}
// temporary coord variable for delta calculation
@@ -68,10 +72,55 @@ const DragMixin = {
// disable mousedown event
this._layer.off('mousedown', this._dragMixinOnMouseDown, this);
},
+ dragging() {
+ return this._dragging;
+ },
+ _dragMixinOnMouseDown(e) {
+ // cancel if mouse button is NOT the left button
+ if (e.originalEvent.button > 0) {
+ return;
+ }
+ // save current map dragging state
+ if (this._safeToCacheDragState) {
+ this._originalMapDragState = this._layer._map.dragging._enabled;
+
+ // don't cache the state again until another mouse up is registered
+ this._safeToCacheDragState = false;
+ }
+
+ // save for delta calculation
+ this._tempDragCoord = e.latlng;
+
+ this._layer._map.on('mouseup', this._dragMixinOnMouseUp, this);
+
+ // listen to mousemove on map (instead of polygon),
+ // otherwise fast mouse movements stop the drag
+ this._layer._map.on('mousemove', this._dragMixinOnMouseMove, this);
+ },
+ _dragMixinOnMouseMove(e) {
+ const el = this._getDOMElem();
+
+ if (!this._dragging) {
+ // set state
+ this._dragging = true;
+ L.DomUtil.addClass(el, 'leaflet-pm-dragging');
+
+ // bring it to front to prevent drag interception
+ this._layer.bringToFront();
+
+ // disbale map drag
+ if (this._originalMapDragState) {
+ this._layer._map.dragging.disable();
+ }
+
+ // fire pm:dragstart event
+ this._fireDragStart();
+ }
+
+ this._onLayerDrag(e);
+ },
_dragMixinOnMouseUp() {
- const el = this._layer._path
- ? this._layer._path
- : this._layer._renderer._container;
+ const el = this._getDOMElem();
// re-enable map drag
if (this._originalMapDragState) {
@@ -92,7 +141,6 @@ const DragMixin = {
return false;
}
-
// update the hidden circle border after dragging
if(this._layer instanceof L.CircleMarker){
this._layer.pm._updateHiddenPolyCircle();
@@ -114,56 +162,6 @@ const DragMixin = {
return true;
},
- _dragMixinOnMouseMove(e) {
- const el = this._layer._path
- ? this._layer._path
- : this._layer._renderer._container;
-
- if (!this._dragging) {
- // set state
- this._dragging = true;
- L.DomUtil.addClass(el, 'leaflet-pm-dragging');
-
- // bring it to front to prevent drag interception
- this._layer.bringToFront();
-
- // disbale map drag
- if (this._originalMapDragState) {
- this._layer._map.dragging.disable();
- }
-
-
- // fire pm:dragstart event
- this._fireDragStart();
- }
-
- this._onLayerDrag(e);
- },
- _dragMixinOnMouseDown(e) {
- // cancel if mouse button is NOT the left button
- if (e.originalEvent.button > 0) {
- return;
- }
- // save current map dragging state
- if (this._safeToCacheDragState) {
- this._originalMapDragState = this._layer._map.dragging._enabled;
-
- // don't cache the state again until another mouse up is registered
- this._safeToCacheDragState = false;
- }
-
- // save for delta calculation
- this._tempDragCoord = e.latlng;
-
- this._layer._map.on('mouseup', this._dragMixinOnMouseUp, this);
-
- // listen to mousemove on map (instead of polygon),
- // otherwise fast mouse movements stop the drag
- this._layer._map.on('mousemove', this._dragMixinOnMouseMove, this);
- },
- dragging() {
- return this._dragging;
- },
_onLayerDrag(e) {
// latLng of mouse event
const { latlng } = e;
@@ -195,6 +193,11 @@ const DragMixin = {
const newCoords = moveCoords([this._layer.getLatLng()]);
// set new coordinates and redraw
this._layer.setLatLng(newCoords[0]);
+ } else if( this._layer instanceof L.ImageOverlay){
+ // create the new coordinates array
+ const newCoords = moveCoords([this._layer.getBounds().getNorthWest(),this._layer.getBounds().getSouthEast()]);
+ // set new coordinates and redraw
+ this._layer.setBounds(newCoords);
} else {
// create the new coordinates array
const newCoords = moveCoords(this._layer.getLatLngs());
@@ -211,31 +214,42 @@ const DragMixin = {
this._fireDrag(e);
},
_fireDragStart() {
- this._layer.fire('pm:dragstart', {
+ Utils._fireEvent(this._layer,'pm:dragstart', {
layer: this._layer,
shape: this.getShape()
});
},
_fireDrag(e) {
- this._layer.fire('pm:drag', Object.assign({},e, {shape:this.getShape()}));
+ Utils._fireEvent(this._layer,'pm:drag', Object.assign({},e, {shape:this.getShape()}));
},
_fireDragEnd() {
- this._layer.fire('pm:dragend', {
+ Utils._fireEvent(this._layer,'pm:dragend', {
layer: this._layer,
shape: this.getShape()
});
},
addDraggingClass() {
- const el = this._layer._path
- ? this._layer._path
- : this._layer._renderer._container;
- L.DomUtil.addClass(el, 'leaflet-pm-draggable');
+ const el = this._getDOMElem();
+ if(el) {
+ L.DomUtil.addClass(el, 'leaflet-pm-draggable');
+ }
},
removeDraggingClass() {
- const el = this._layer._path
- ? this._layer._path
- : this._layer._renderer._container;
- L.DomUtil.removeClass(el, 'leaflet-pm-draggable');
+ const el = this._getDOMElem();
+ if(el) {
+ L.DomUtil.removeClass(el, 'leaflet-pm-draggable');
+ }
+ },
+ _getDOMElem(){
+ let el = null;
+ if(this._layer._path){
+ el = this._layer._path;
+ }else if(this._layer._renderer && this._layer._renderer._container){
+ el = this._layer._renderer._container;
+ }else if(this._layer._image){
+ el = this._layer._image;
+ }
+ return el;
}
};
diff --git a/src/js/Mixins/Modes/Mode.Drag.js b/src/js/Mixins/Modes/Mode.Drag.js
index 4352f0f4..b7dbb27b 100644
--- a/src/js/Mixins/Modes/Mode.Drag.js
+++ b/src/js/Mixins/Modes/Mode.Drag.js
@@ -1,11 +1,8 @@
import Utils from '../../L.PM.Utils'
-const { findLayers } = Utils
+const { findLayers } = Utils;
const GlobalDragMode = {
- globalDragModeEnabled() {
- return !!this._globalDragMode;
- },
enableGlobalDragMode() {
const layers = findLayers(this.map);
@@ -44,11 +41,8 @@ const GlobalDragMode = {
this._fireDragModeEvent(false);
},
- _fireDragModeEvent(enabled) {
- this.map.fire('pm:globaldragmodetoggled', {
- enabled,
- map: this.map,
- });
+ globalDragModeEnabled() {
+ return !!this._globalDragMode;
},
toggleGlobalDragMode() {
if (this.globalDragModeEnabled()) {
@@ -58,7 +52,7 @@ const GlobalDragMode = {
}
},
reinitGlobalDragMode({ layer }) {
- // do nothing if layer is not handled by leaflet so it doesn't fire unnecessarily
+ // do nothing if layer is not handled by leaflet so it doesn't fire unnecessarily
const isRelevant = !!layer.pm && !layer._pmTempLayer;
if (!isRelevant) {
return;
@@ -70,6 +64,12 @@ const GlobalDragMode = {
this.enableGlobalDragMode();
}
},
+ _fireDragModeEvent(enabled) {
+ Utils._fireEvent(this.map,'pm:globaldragmodetoggled', {
+ enabled,
+ map: this.map,
+ });
+ },
}
-export default GlobalDragMode
\ No newline at end of file
+export default GlobalDragMode
diff --git a/src/js/Mixins/Modes/Mode.Edit.js b/src/js/Mixins/Modes/Mode.Edit.js
index fd4427a7..44214bf6 100644
--- a/src/js/Mixins/Modes/Mode.Edit.js
+++ b/src/js/Mixins/Modes/Mode.Edit.js
@@ -1,29 +1,15 @@
// this mixin adds a global edit mode to the map
import Utils from '../../L.PM.Utils'
-const { findLayers } = Utils
+const { findLayers } = Utils;
const GlobalEditMode = {
_globalEditMode: false,
- // TODO: Remove in the next major release
- globalEditEnabled() {
- return this.globalEditModeEnabled();
- },
- globalEditModeEnabled() {
- return this._globalEditMode;
- },
- setGlobalEditStatus(status) {
- // set status
- this._globalEditMode = status;
-
- // fire event
- this._fireEditModeEvent(this._globalEditMode);
- },
enableGlobalEditMode(o) {
const options = {
snappable: this._globalSnappingEnabled,
...o
- }
+ };
const status = true;
@@ -42,6 +28,9 @@ const GlobalEditMode = {
this.throttledReInitEdit = L.Util.throttle(this.handleLayerAdditionInGlobalEditMode, 100, this)
}
+ // save the added layers into the _addedLayers array, to read it later out
+ this._addedLayers = [];
+ this.map.on('layeradd', this._layerAdded, this);
// handle layers that are added while in removal mode
this.map.on('layeradd', this.throttledReInitEdit, this);
@@ -59,16 +48,27 @@ const GlobalEditMode = {
});
// cleanup layer off event
- this.map.off('layeroff', this.throttledReInitEdit, this);
+ this.map.off('layeradd', this.throttledReInitEdit, this);
// Set toolbar button to currect status
this.Toolbar.toggleButton('editMode', status);
this.setGlobalEditStatus(status);
},
+ // TODO: Remove in the next major release
+ globalEditEnabled() {
+ return this.globalEditModeEnabled();
+ },
+ globalEditModeEnabled() {
+ return this._globalEditMode;
+ },
+ setGlobalEditStatus(status) {
+ // set status
+ this._globalEditMode = status;
+ // fire event
+ this._fireEditModeEvent(this._globalEditMode);
+ },
toggleGlobalEditMode(options = this.globalOptions) {
- // console.log('toggle global edit mode', options);
-
if (this.globalEditModeEnabled()) {
// disable
this.disableGlobalEditMode();
@@ -77,27 +77,33 @@ const GlobalEditMode = {
this.enableGlobalEditMode(options);
}
},
- handleLayerAdditionInGlobalEditMode({ layer }) {
- // when global edit mode is enabled and a layer is added to the map,
- // enable edit for that layer if it's relevant
-
- // do nothing if layer is not handled by leaflet so it doesn't fire unnecessarily
- const isRelevant = !!layer.pm && !layer._pmTempLayer;
- if (!isRelevant) {
- return;
- }
-
- if (this.globalEditModeEnabled()) {
- layer.pm.enable({ ...this.globalOptions, snappable: this._globalSnappingEnabled });
- }
+ handleLayerAdditionInGlobalEditMode() {
+ const layers = this._addedLayers;
+ this._addedLayers = [];
+ layers.forEach((layer)=> {
+ // when global edit mode is enabled and a layer is added to the map,
+ // enable edit for that layer if it's relevant
+
+ // do nothing if layer is not handled by leaflet so it doesn't fire unnecessarily
+ const isRelevant = !!layer.pm && !layer._pmTempLayer;
+ if (!isRelevant) {
+ return;
+ }
+
+ if (this.globalEditModeEnabled()) {
+ layer.pm.enable({...this.globalOptions});
+ }
+ });
+ },
+ _layerAdded({layer}){
+ this._addedLayers.push(layer);
},
_fireEditModeEvent(enabled) {
- this.map.fire('pm:globaleditmodetoggled', {
+ Utils._fireEvent(this.map,'pm:globaleditmodetoggled', {
enabled,
map: this.map,
});
},
-
-}
+};
export default GlobalEditMode
diff --git a/src/js/Mixins/Modes/Mode.Removal.js b/src/js/Mixins/Modes/Mode.Removal.js
index b22f99b5..448e2bd1 100644
--- a/src/js/Mixins/Modes/Mode.Removal.js
+++ b/src/js/Mixins/Modes/Mode.Removal.js
@@ -1,18 +1,6 @@
-const GlobalRemovalMode = {
- disableGlobalRemovalMode() {
- this._globalRemovalMode = false;
- this.map.eachLayer(layer => {
- layer.off('click', this.removeLayer, this);
- });
-
- // remove map handler
- this.map.off('layeradd', this.throttledReInitRemoval, this);
+import Utils from "../../L.PM.Utils";
- // toogle the button in the toolbar if this is called programatically
- this.Toolbar.toggleButton('deleteLayer', this._globalRemovalMode);
-
- this._fireRemovalModeEvent(false);
- },
+const GlobalRemovalMode = {
enableGlobalRemovalMode() {
const isRelevant = layer =>
layer.pm &&
@@ -39,11 +27,26 @@ const GlobalRemovalMode = {
this._fireRemovalModeEvent(true);
},
- _fireRemovalModeEvent(enabled) {
- this.map.fire('pm:globalremovalmodetoggled', {
- enabled,
- map: this.map,
+ disableGlobalRemovalMode() {
+ this._globalRemovalMode = false;
+ this.map.eachLayer(layer => {
+ layer.off('click', this.removeLayer, this);
});
+
+ // remove map handler
+ this.map.off('layeradd', this.throttledReInitRemoval, this);
+
+ // toogle the button in the toolbar if this is called programatically
+ this.Toolbar.toggleButton('deleteLayer', this._globalRemovalMode);
+
+ this._fireRemovalModeEvent(false);
+ },
+ // TODO: Remove in the next major release
+ globalRemovalEnabled() {
+ return this.globalRemovalModeEnabled();
+ },
+ globalRemovalModeEnabled() {
+ return !!this._globalRemovalMode;
},
toggleGlobalRemovalMode() {
// toggle global edit mode
@@ -53,15 +56,26 @@ const GlobalRemovalMode = {
this.enableGlobalRemovalMode();
}
},
- // TODO: Remove in the next major release
- globalRemovalEnabled() {
- return this.globalRemovalModeEnabled();
+ reinitGlobalRemovalMode({ layer }) {
+ // do nothing if layer is not handled by leaflet so it doesn't fire unnecessarily
+ const isRelevant = !!layer.pm && !layer._pmTempLayer;
+ if (!isRelevant) {
+ return;
+ }
+
+ // re-enable global removal mode if it's enabled already
+ if (this.globalRemovalModeEnabled()) {
+ this.disableGlobalRemovalMode();
+ this.enableGlobalRemovalMode();
+ }
},
- globalRemovalModeEnabled() {
- return !!this._globalRemovalMode;
+ _fireRemovalModeEvent(enabled) {
+ Utils._fireEvent(this.map,'pm:globalremovalmodetoggled', {
+ enabled,
+ map: this.map,
+ });
},
removeLayer(e) {
-
const layer = e.target;
// only remove layer, if it's handled by leaflet-geoman,
// not a tempLayer and not currently being dragged
@@ -69,30 +83,18 @@ const GlobalRemovalMode = {
!layer._pmTempLayer && (!layer.pm || !layer.pm.dragging());
if (removeable) {
+ layer.removeFrom(this.map.pm._getContainingLayer());
layer.remove();
if(layer instanceof L.LayerGroup){
- layer.fire('pm:remove', { layer, shape: undefined });
- this.map.fire('pm:remove', { layer, shape: undefined });
+ Utils._fireEvent(layer,'pm:remove', { layer, shape: undefined });
+ Utils._fireEvent(this.map,'pm:remove', { layer, shape: undefined });
}else{
- layer.fire('pm:remove', { layer, shape: layer.pm.getShape() });
- this.map.fire('pm:remove', { layer, shape: layer.pm.getShape() });
+ Utils._fireEvent(layer,'pm:remove', { layer, shape: layer.pm.getShape() });
+ Utils._fireEvent(this.map,'pm:remove', { layer, shape: layer.pm.getShape() });
}
}
},
- reinitGlobalRemovalMode({ layer }) {
- // do nothing if layer is not handled by leaflet so it doesn't fire unnecessarily
- const isRelevant = !!layer.pm && !layer._pmTempLayer;
- if (!isRelevant) {
- return;
- }
-
- // re-enable global removal mode if it's enabled already
- if (this.globalRemovalModeEnabled()) {
- this.disableGlobalRemovalMode();
- this.enableGlobalRemovalMode();
- }
- },
-}
+};
export default GlobalRemovalMode
diff --git a/src/js/Mixins/Snapping.js b/src/js/Mixins/Snapping.js
index 4663a658..0fa0b010 100644
--- a/src/js/Mixins/Snapping.js
+++ b/src/js/Mixins/Snapping.js
@@ -1,4 +1,5 @@
import Utils from '../L.PM.Utils';
+import {isEmptyDeep, prioritiseSort} from "../helpers";
const SnapMixin = {
_initSnappableMarkers() {
@@ -30,15 +31,16 @@ const SnapMixin = {
marker.on('dragend', this._cleanupSnapping, this);
});
},
- _unsnap() {
- // delete the last snap
- delete this._snapLatLng;
- },
_cleanupSnapping() {
// delete it, we need to refresh this with each start of a drag because
// meanwhile, new layers could've been added to the map
delete this._snapList;
+ if (this.throttledList) {
+ this._map.off('layeradd', this.throttledList, this);
+ this.throttledList = undefined;
+ }
+
// remove map event
this._map.off('pm:remove', this._handleSnapLayerRemoval, this);
@@ -48,17 +50,10 @@ const SnapMixin = {
});
}
},
- _handleSnapLayerRemoval({ layer }) {
- // find the layers index in snaplist
- const index = this._snapList.findIndex(
- e => e._leaflet_id === layer._leaflet_id
- );
- // remove it from the snaplist
- this._snapList.splice(index, 1);
- },
_handleSnapping(e) {
- function throttledList() {
- return L.Util.throttle(this._createSnapList, 100, this);
+
+ if (!this.throttledList) {
+ this.throttledList = L.Util.throttle(this._createSnapList, 100, this);
}
// if snapping is disabled via holding ALT during drag, stop right here
@@ -73,8 +68,8 @@ const SnapMixin = {
this._createSnapList();
// re-create the snaplist again when a layer is added during draw
- this._map.off('layeradd', throttledList, this);
- this._map.on('layeradd', throttledList, this);
+ this._map.off('layeradd', this.throttledList, this);
+ this._map.on('layeradd', this.throttledList, this);
}
// if there are no layers to snap to, stop here
@@ -91,7 +86,7 @@ const SnapMixin = {
);
// if no layers found. Can happen when circle is the only visible layer on the map and the hidden snapping-border circle layer is also on the map
- if(Object.keys(closestLayer).length === 0){
+ if (Object.keys(closestLayer).length === 0) {
return false;
}
@@ -122,19 +117,21 @@ const SnapMixin = {
distance: closestLayer.distance,
};
- eventInfo.marker.fire('pm:snapdrag', eventInfo);
- this._layer.fire('pm:snapdrag', eventInfo);
+ Utils._fireEvent(eventInfo.marker,'pm:snapdrag', eventInfo);
+ Utils._fireEvent(this._layer,'pm:snapdrag', eventInfo);
if (closestLayer.distance < minDistance) {
// snap the marker
+ marker._orgLatLng = marker.getLatLng();
marker.setLatLng(snapLatLng);
marker._snapped = true;
+ marker._snapInfo = eventInfo;
const triggerSnap = () => {
this._snapLatLng = snapLatLng;
- marker.fire('pm:snap', eventInfo);
- this._layer.fire('pm:snap', eventInfo);
+ Utils._fireEvent(marker,'pm:snap', eventInfo);
+ Utils._fireEvent(this._layer,'pm:snap', eventInfo);
};
// check if the snapping position differs from the last snap
@@ -155,66 +152,12 @@ const SnapMixin = {
marker._snapped = false;
// and fire unsnap event
- eventInfo.marker.fire('pm:unsnap', eventInfo);
- this._layer.fire('pm:unsnap', eventInfo);
+ Utils._fireEvent(eventInfo.marker,'pm:unsnap', eventInfo);
+ Utils._fireEvent(this._layer,'pm:unsnap', eventInfo);
}
return true;
},
-
- // we got the point we want to snap to (C), but we need to check if a coord of the polygon
- // receives priority over C as the snapping point. Let's check this here
- _checkPrioritiySnapping(closestLayer) {
- const map = this._map;
-
- // A and B are the points of the closest segment to P (the marker position we want to snap)
- const A = closestLayer.segment[0];
- const B = closestLayer.segment[1];
-
- // C is the point we would snap to on the segment.
- // The closest point on the closest segment of the closest polygon to P. That's right.
- const C = closestLayer.latlng;
-
- // distances from A to C and B to C to check which one is closer to C
- const distanceAC = this._getDistance(map, A, C);
- const distanceBC = this._getDistance(map, B, C);
-
- // closest latlng of A and B to C
- let closestVertexLatLng = distanceAC < distanceBC ? A : B;
-
- // distance between closestVertexLatLng and C
- let shortestDistance = distanceAC < distanceBC ? distanceAC : distanceBC;
-
- // snap to middle (M) of segment if option is enabled
- if (this.options.snapMiddle) {
- const M = Utils.calcMiddleLatLng(map, A, B);
- const distanceMC = this._getDistance(map, M, C);
-
- if (distanceMC < distanceAC && distanceMC < distanceBC) {
- // M is the nearest vertex
- closestVertexLatLng = M;
- shortestDistance = distanceMC;
- }
- }
-
- // the distance that needs to be undercut to trigger priority
- const priorityDistance = this.options.snapDistance;
-
- // the latlng we ultemately want to snap to
- let snapLatlng;
-
- // if C is closer to the closestVertexLatLng (A, B or M) than the snapDistance,
- // the closestVertexLatLng has priority over C as the snapping point.
- if (shortestDistance < priorityDistance) {
- snapLatlng = closestVertexLatLng;
- } else {
- snapLatlng = C;
- }
-
- // return the copy of snapping point
- return Object.assign({}, snapLatlng);
- },
-
_createSnapList() {
let layers = [];
const debugIndicatorLines = [];
@@ -229,18 +172,24 @@ const SnapMixin = {
if (
(layer instanceof L.Polyline ||
layer instanceof L.Marker ||
- layer instanceof L.CircleMarker) &&
- layer.options.snapIgnore !== true
+ layer instanceof L.CircleMarker ||
+ layer instanceof L.ImageOverlay) &&
+ layer.options.snapIgnore !== true &&
+ (
+ (!L.PM.optIn && !layer.options.pmIgnore) || // if optIn is not set / true and pmIgnore is not set / true (default)
+ (L.PM.optIn && layer.options.pmIgnore === false) // if optIn is true and pmIgnore is false
+ )
) {
-
// adds a hidden polygon which matches the border of the circle
- if ((layer instanceof L.Circle || layer instanceof L.CircleMarker) && layer.pm._hiddenPolyCircle) {
+ if ((layer instanceof L.Circle || layer instanceof L.CircleMarker) && layer.pm && layer.pm._hiddenPolyCircle) {
layers.push(layer.pm._hiddenPolyCircle);
+ } else if (layer instanceof L.ImageOverlay) {
+ layer = L.rectangle(layer.getBounds());
}
layers.push(layer);
// this is for debugging
- const debugLine = L.polyline([], { color: 'red', pmIgnore: true });
+ const debugLine = L.polyline([], {color: 'red', pmIgnore: true});
debugLine._pmTempLayer = true;
debugIndicatorLines.push(debugLine);
if (layer instanceof L.Circle || layer instanceof L.CircleMarker) {
@@ -257,7 +206,7 @@ const SnapMixin = {
// also remove everything that has no coordinates yet
layers = layers.filter(
- layer => layer._latlng || (layer._latlngs && layer._latlngs.length > 0)
+ layer => layer._latlng || (layer._latlngs && !isEmptyDeep(layer._latlngs))
);
// finally remove everything that's leaflet-geoman specific temporary stuff
@@ -272,8 +221,17 @@ const SnapMixin = {
this.debugIndicatorLines = debugIndicatorLines;
},
+ _handleSnapLayerRemoval({layer}) {
+ // find the layers index in snaplist
+ const index = this._snapList.findIndex(
+ e => e._leaflet_id === layer._leaflet_id
+ );
+ // remove it from the snaplist
+ this._snapList.splice(index, 1);
+ },
_calcClosestLayer(latlng, layers) {
// the closest polygon to our dragged marker latlng
+ let closestLayers = [];
let closestLayer = {};
// loop through the layers
@@ -291,18 +249,21 @@ const SnapMixin = {
// save the info if it doesn't exist or if the distance is smaller than the previous one
if (
closestLayer.distance === undefined ||
- results.distance < closestLayer.distance
+ results.distance <= closestLayer.distance
) {
+ if (results.distance < closestLayer.distance) {
+ closestLayers = [];
+ }
closestLayer = results;
closestLayer.layer = layer;
+ closestLayers.push(closestLayer);
}
});
// return the closest layer and it's data
- // if there is no closest layer, return undefined
- return closestLayer;
+ // if there is no closest layer, return an empty object
+ return this._getClosestLayerByPriority(closestLayers);
},
-
_calcLayerDistances(latlng, layer) {
const map = this._map;
@@ -384,7 +345,83 @@ const SnapMixin = {
distance: shortestDistance,
};
},
+ _getClosestLayerByPriority(layers) {
+ // sort the layers by creation, so it is snapping to the oldest layer from the same shape
+ layers = layers.sort((a, b) => a._leaflet_id - b._leaflet_id);
+
+ const shapes = ['Marker', 'CircleMarker', 'Circle', 'Line', 'Polygon', 'Rectangle'];
+ const order = this._map.pm.globalOptions.snappingOrder || [];
+
+ let lastIndex = 0;
+ const prioOrder = {};
+ // merge user-preferred priority with default priority
+ order.concat(shapes).forEach((shape) => {
+ if (!prioOrder[shape]) {
+ lastIndex += 1;
+ prioOrder[shape] = lastIndex;
+ }
+ });
+
+ // sort layers by priority
+ layers.sort(prioritiseSort('instanceofShape', prioOrder));
+ return layers[0] || {};
+ },
+ // we got the point we want to snap to (C), but we need to check if a coord of the polygon
+ // receives priority over C as the snapping point. Let's check this here
+ _checkPrioritiySnapping(closestLayer) {
+ const map = this._map;
+
+ // A and B are the points of the closest segment to P (the marker position we want to snap)
+ const A = closestLayer.segment[0];
+ const B = closestLayer.segment[1];
+
+ // C is the point we would snap to on the segment.
+ // The closest point on the closest segment of the closest polygon to P. That's right.
+ const C = closestLayer.latlng;
+
+ // distances from A to C and B to C to check which one is closer to C
+ const distanceAC = this._getDistance(map, A, C);
+ const distanceBC = this._getDistance(map, B, C);
+
+ // closest latlng of A and B to C
+ let closestVertexLatLng = distanceAC < distanceBC ? A : B;
+
+ // distance between closestVertexLatLng and C
+ let shortestDistance = distanceAC < distanceBC ? distanceAC : distanceBC;
+
+ // snap to middle (M) of segment if option is enabled
+ if (this.options.snapMiddle) {
+ const M = Utils.calcMiddleLatLng(map, A, B);
+ const distanceMC = this._getDistance(map, M, C);
+
+ if (distanceMC < distanceAC && distanceMC < distanceBC) {
+ // M is the nearest vertex
+ closestVertexLatLng = M;
+ shortestDistance = distanceMC;
+ }
+ }
+
+ // the distance that needs to be undercut to trigger priority
+ const priorityDistance = this.options.snapDistance;
+
+ // the latlng we ultemately want to snap to
+ let snapLatlng;
+ // if C is closer to the closestVertexLatLng (A, B or M) than the snapDistance,
+ // the closestVertexLatLng has priority over C as the snapping point.
+ if (shortestDistance < priorityDistance) {
+ snapLatlng = closestVertexLatLng;
+ } else {
+ snapLatlng = C;
+ }
+
+ // return the copy of snapping point
+ return Object.assign({}, snapLatlng);
+ },
+ _unsnap() {
+ // delete the last snap
+ delete this._snapLatLng;
+ },
_getClosestPointOnSegment(map, latlng, latlngA, latlngB) {
let maxzoom = map.getMaxZoom();
if (maxzoom === Infinity) {
diff --git a/src/js/Toolbar/L.Controls.js b/src/js/Toolbar/L.Controls.js
index 8fddebed..fe9356a7 100644
--- a/src/js/Toolbar/L.Controls.js
+++ b/src/js/Toolbar/L.Controls.js
@@ -1,4 +1,5 @@
import { getTranslation } from '../helpers';
+import Utils from "../L.PM.Utils";
const PMButton = L.Control.extend({
options: {
@@ -136,8 +137,23 @@ const PMButton = L.Control.extend({
actionNode.innerHTML = action.text;
- if (action.onClick) {
- L.DomEvent.addListener(actionNode, 'click', action.onClick, this);
+ if(!button.disabled) {
+ if (action.onClick) {
+ const actionClick = () => {
+ let btnName = "";
+ const {buttons} = this._map.pm.Toolbar;
+ for (const btn in buttons) {
+ if (buttons[btn]._button === button) {
+ btnName = btn;
+ break;
+ }
+ }
+ Utils._fireEvent(this._map,'pm:actionclick', {text: action.text, action, btnName, button});
+ };
+
+ L.DomEvent.addListener(actionNode, 'click', actionClick, this);
+ L.DomEvent.addListener(actionNode, 'click', action.onClick, this);
+ }
}
L.DomEvent.disableClickPropagation(actionNode);
});
@@ -158,14 +174,30 @@ const PMButton = L.Control.extend({
if (button.className) {
L.DomUtil.addClass(image, button.className);
}
- // before the actual click, trigger a click on currently toggled buttons to
- // untoggle them and their functionality
- L.DomEvent.addListener(newButton, 'click', () => {
- if (this._button.disableOtherButtons) {
- this._map.pm.Toolbar.triggerClickOnToggledButtons(this);
- }
- });
- L.DomEvent.addListener(newButton, 'click', this._triggerClick, this);
+ if(!button.disabled) {
+ // before the actual click, trigger a click on currently toggled buttons to
+ // untoggle them and their functionality
+ L.DomEvent.addListener(newButton, 'click', () => {
+ if (this._button.disableOtherButtons) {
+ this._map.pm.Toolbar.triggerClickOnToggledButtons(this);
+ }
+ let btnName = "";
+ const {buttons} = this._map.pm.Toolbar;
+ for (const btn in buttons) {
+ if (buttons[btn]._button === button) {
+ btnName = btn;
+ break;
+ }
+ }
+ Utils._fireEvent(this._map,'pm:buttonclick', {btnName, button});
+ });
+ L.DomEvent.addListener(newButton, 'click', this._triggerClick, this);
+ }
+
+ if(button.disabled){
+ L.DomUtil.addClass(newButton, 'pm-disabled');
+ L.DomUtil.addClass(image, 'pm-disabled');
+ }
L.DomEvent.disableClickPropagation(newButton);
return buttonContainer;
diff --git a/src/js/Toolbar/L.PM.Toolbar.js b/src/js/Toolbar/L.PM.Toolbar.js
index ffb44060..9ac09e99 100644
--- a/src/js/Toolbar/L.PM.Toolbar.js
+++ b/src/js/Toolbar/L.PM.Toolbar.js
@@ -429,21 +429,7 @@ const Toolbar = L.Class.extend({
options = { name: options };
}
- const shapeMapping = {
- "Marker": "drawMarker",
- "Circle": "drawCircle",
- "Polygon": "drawPolygon",
- "Rectangle": "drawRectangle",
- "Polyline": "drawPolyline",
- "Line": "drawPolyline",
- "CircleMarker": "drawCircleMarker",
- "Edit": "editMode",
- "Drag": "dragMode",
- "Cut": "cutPolygon",
- "Removal": "removalMode"
- };
-
- const instance = shapeMapping[copyInstance] ? shapeMapping[copyInstance] : copyInstance;
+ const instance = this._btnNameMapping(copyInstance);
if (!options.name) {
throw new TypeError(
@@ -492,7 +478,9 @@ const Toolbar = L.Class.extend({
options.block = "";
}
- if (options.className.indexOf('control-icon') === -1) {
+ if (!options.className){
+ options.className = 'control-icon';
+ }else if(options.className.indexOf('control-icon') === -1) {
options.className = `control-icon ${options.className}`;
}
@@ -509,6 +497,7 @@ const Toolbar = L.Class.extend({
cssToggle: options.toggle,
position: this.options.position,
actions: options.actions || [],
+ disabled: !!options.disabled,
};
if (this.options[options.name] !== false) {
@@ -521,19 +510,7 @@ const Toolbar = L.Class.extend({
},
changeControlOrder(order = []) {
- const shapeMapping = {
- "Marker": "drawMarker",
- "Circle": "drawCircle",
- "Polygon": "drawPolygon",
- "Rectangle": "drawRectangle",
- "Polyline": "drawPolyline",
- "Line": "drawPolyline",
- "CircleMarker": "drawCircleMarker",
- "Edit": "editMode",
- "Drag": "dragMode",
- "Cut": "cutPolygon",
- "Removal": "removalMode"
- };
+ const shapeMapping = this._shapeMapping();
const _order = [];
order.forEach((shape) => {
@@ -597,21 +574,7 @@ const Toolbar = L.Class.extend({
return order;
},
changeActionsOfControl(name, actions) {
- const shapeMapping = {
- "Marker": "drawMarker",
- "Circle": "drawCircle",
- "Polygon": "drawPolygon",
- "Rectangle": "drawRectangle",
- "Polyline": "drawPolyline",
- "Line": "drawPolyline",
- "CircleMarker": "drawCircleMarker",
- "Edit": "editMode",
- "Drag": "dragMode",
- "Cut": "cutPolygon",
- "Removal": "removalMode"
- };
-
- const btnName = shapeMapping[name] ? shapeMapping[name] : name;
+ const btnName = this._btnNameMapping(name);
if (!btnName) {
throw new TypeError(
@@ -631,6 +594,30 @@ const Toolbar = L.Class.extend({
}
this.buttons[btnName]._button.actions = actions;
this.changeControlOrder();
+ },
+ setButtonDisabled(name,state){
+ const btnName = this._btnNameMapping(name);
+ this.buttons[btnName]._button.disabled = !!state;
+ this._showHideButtons();
+ },
+ _shapeMapping(){
+ return {
+ "Marker": "drawMarker",
+ "Circle": "drawCircle",
+ "Polygon": "drawPolygon",
+ "Rectangle": "drawRectangle",
+ "Polyline": "drawPolyline",
+ "Line": "drawPolyline",
+ "CircleMarker": "drawCircleMarker",
+ "Edit": "editMode",
+ "Drag": "dragMode",
+ "Cut": "cutPolygon",
+ "Removal": "removalMode"
+ }
+ },
+ _btnNameMapping(name){
+ const shapeMapping = this._shapeMapping();
+ return shapeMapping[name] ? shapeMapping[name] : name;
}
});
diff --git a/src/js/helpers/index.js b/src/js/helpers/index.js
index d15014bb..4ad24d3d 100644
--- a/src/js/helpers/index.js
+++ b/src/js/helpers/index.js
@@ -22,6 +22,15 @@ export function isEmptyDeep(l) {
return !flatten(l).length;
}
+export function removeEmptyCoordRings(arr) {
+ return arr.reduce((result, item)=>{
+ if(item.length !== 0){
+ result.push( Array.isArray(item) ? removeEmptyCoordRings(item) : item );
+ }
+ return result;
+ }, []);
+}
+
// Code from https://stackoverflow.com/a/24153998/8283938
function destinationVincenty(lonlat, brng, dist) { // rewritten to work with leaflet
const VincentyConstants = {
@@ -30,7 +39,7 @@ function destinationVincenty(lonlat, brng, dist) { // rewritten to work with lea
f: 1 / 298.257223563
};
- const { a, b, f } = VincentyConstants;
+ const {a, b, f} = VincentyConstants;
const lon1 = lonlat.lng;
const lat1 = lonlat.lat;
const s = dist;
@@ -75,6 +84,7 @@ function destinationVincenty(lonlat, brng, dist) { // rewritten to work with lea
return L.latLng(lamFunc, lat2a);
}
+
export function createGeodesicPolygon(origin, radius, sides, rotation) {
let angle;
let newLonlat;
@@ -90,3 +100,105 @@ export function createGeodesicPolygon(origin, radius, sides, rotation) {
return points;
}
+
+/* Copied from L.GeometryUtil */
+function destination(latlng, heading, distance) {
+ heading = (heading + 360) % 360;
+ const rad = Math.PI / 180;
+ const radInv = 180 / Math.PI;
+ const R = 6378137; // approximation of Earth's radius
+ const lon1 = latlng.lng * rad;
+ const lat1 = latlng.lat * rad;
+ const rheading = heading * rad;
+ const sinLat1 = Math.sin(lat1);
+ const cosLat1 = Math.cos(lat1);
+ const cosDistR = Math.cos(distance / R);
+ const sinDistR = Math.sin(distance / R);
+ const lat2 = Math.asin(sinLat1 * cosDistR + cosLat1 * sinDistR * Math.cos(rheading));
+ let lon2 = lon1 + Math.atan2(Math.sin(rheading) * sinDistR * cosLat1, cosDistR - sinLat1 * Math.sin(lat2));
+ lon2 *= radInv;
+ lon2 = lon2 > 180 ? lon2 - 360 : lon2 < -180 ? lon2 + 360 : lon2;
+ return L.latLng([lat2 * radInv, lon2]);
+}
+/* Copied from L.GeometryUtil */
+function angle(map, latlngA, latlngB) {
+ const pointA = map.latLngToContainerPoint(latlngA);
+ const pointB = map.latLngToContainerPoint(latlngB);
+ let angleDeg = Math.atan2(pointB.y - pointA.y, pointB.x - pointA.x) * 180 / Math.PI + 90;
+ angleDeg += angleDeg < 0 ? 360 : 0;
+ return angleDeg;
+
+}
+
+export function destinationOnLine(map, latlngA, latlngB, distance) {
+ const angleDeg = angle(map, latlngA, latlngB);
+ return destination(latlngA, angleDeg, distance);
+}
+
+
+// this function is used with the .sort(prioritiseSort(key, sortingOrder)) function of arrays
+export function prioritiseSort(key, _sortingOrder, order = 'asc') {
+ /* the sorting order has all possible keys (lowercase) with the index and then it is sorted by the key on the object */
+
+ if(!_sortingOrder || Object.keys(_sortingOrder).length === 0) {
+ return (a,b)=>a-b; // default sort method
+ }
+
+ // change the keys to lowercase
+ const keys = Object.keys(_sortingOrder);
+ let objKey;
+ let n = keys.length;
+ const sortingOrder={};
+ while (n--) {
+ objKey = keys[n];
+ sortingOrder[objKey.toLowerCase()] = _sortingOrder[objKey];
+ }
+
+ function getShape(layer){
+ if(layer instanceof L.Marker){
+ return "Marker";
+ }else if(layer instanceof L.Circle){
+ return "Circle";
+ }else if(layer instanceof L.CircleMarker){
+ return "CircleMarker";
+ }else if(layer instanceof L.Rectangle){
+ return "Rectangle";
+ }else if(layer instanceof L.Polygon){
+ return "Polygon";
+ }else if(layer instanceof L.Polyline){
+ return "Line";
+ }else{
+ return undefined;
+ }
+ }
+
+ return (a, b) => {
+ let keyA;
+ let keyB;
+ if(key === "instanceofShape"){
+ keyA = getShape(a.layer).toLowerCase();
+ keyB = getShape(b.layer).toLowerCase();
+ if(!keyA || !keyB) return 0;
+ }else{
+ /* eslint-disable-next-line no-prototype-builtins */
+ if (!a.hasOwnProperty(key) || !b.hasOwnProperty(key)) return 0;
+ keyA = a[key].toLowerCase();
+ keyB = b[key].toLowerCase();
+ }
+
+ const first =
+ keyA in sortingOrder
+ ? sortingOrder[keyA]
+ : Number.MAX_SAFE_INTEGER;
+
+ const second =
+ keyB in sortingOrder
+ ? sortingOrder[keyB]
+ : Number.MAX_SAFE_INTEGER;
+
+ let result = 0;
+ if (first < second) result = -1;
+ else if (first > second) result = 1;
+ return order === 'desc' ? (result * -1) : result;
+ }
+}