mirror of
https://github.com/Leaflet/Leaflet.Editable.git
synced 2025-07-23 00:23:20 +00:00
Initial commit
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
npm-debug.log
|
||||
node_modules/*
|
23
README.md
Normal file
23
README.md
Normal file
@ -0,0 +1,23 @@
|
||||
# Leaflet.Editable
|
||||
|
||||
Make geometries editable in Leaflet.
|
||||
|
||||
This is not a plug and play UI, and will not. This is a minimal, lightweight,
|
||||
and fully extendable API to control editing of geometries. So you can easily
|
||||
build your own UI with your own needs and choices.
|
||||
|
||||
See the [demo UI](http://yohanboniface.github.io/Leaflet.Editable/example/index.html).
|
||||
|
||||
Design keys:
|
||||
|
||||
- only the core needs
|
||||
- no UI, but hooks everywhere needed
|
||||
- everything programatically controlable
|
||||
- MultiPolygon/MultiPolyline support
|
||||
- Polygons' holes support
|
||||
- touch support
|
||||
- tests
|
||||
|
||||
|
||||
**Considered pre-alpha. Under active developpement, many things can change
|
||||
before a proprer release.**
|
131
example/example.js
Normal file
131
example/example.js
Normal file
@ -0,0 +1,131 @@
|
||||
L.NewLineControl = L.Control.extend({
|
||||
|
||||
options: {
|
||||
position: 'topleft'
|
||||
},
|
||||
|
||||
onAdd: function (map) {
|
||||
var container = L.DomUtil.create('div', 'leaflet-control leaflet-bar'),
|
||||
link = L.DomUtil.create('a', '', container);
|
||||
|
||||
link.href = '#';
|
||||
link.title = 'Create a new line';
|
||||
link.innerHTML = '/\\/';
|
||||
L.DomEvent.on(link, 'click', L.DomEvent.stop)
|
||||
.on(link, 'click', function () {
|
||||
map.editable.startNewLine();
|
||||
});
|
||||
|
||||
return container;
|
||||
}
|
||||
});
|
||||
|
||||
L.NewPolygonControl = L.Control.extend({
|
||||
|
||||
options: {
|
||||
position: 'topleft'
|
||||
},
|
||||
|
||||
onAdd: function (map) {
|
||||
var container = L.DomUtil.create('div', 'leaflet-control leaflet-bar'),
|
||||
link = L.DomUtil.create('a', '', container);
|
||||
|
||||
link.href = '#';
|
||||
link.title = 'Create a new polygon';
|
||||
link.innerHTML = '▱';
|
||||
L.DomEvent.on(link, 'click', L.DomEvent.stop)
|
||||
.on(link, 'click', function () {
|
||||
map.editable.startNewPolygon();
|
||||
});
|
||||
|
||||
return container;
|
||||
}
|
||||
});
|
||||
|
||||
L.NewHoleControl = L.Control.extend({
|
||||
|
||||
options: {
|
||||
position: 'topleft'
|
||||
},
|
||||
|
||||
onAdd: function (map) {
|
||||
var container = L.DomUtil.create('div', 'leaflet-control leaflet-bar'),
|
||||
link = L.DomUtil.create('a', '', container);
|
||||
|
||||
link.href = '#';
|
||||
link.title = 'Create a new hole';
|
||||
link.innerHTML = '▣';
|
||||
L.DomEvent.on(link, 'click', L.DomEvent.stop)
|
||||
.on(link, 'click', function () {
|
||||
map.editable.startNewHole();
|
||||
});
|
||||
|
||||
var toggle = function (e) {
|
||||
if (e && e.editor && e.editor instanceof L.Editable.PolygonEditor) {
|
||||
container.style.display = 'block';
|
||||
} else {
|
||||
container.style.display = 'none';
|
||||
}
|
||||
};
|
||||
toggle();
|
||||
|
||||
map.editable.on('editable:editorchanged', toggle);
|
||||
|
||||
return container;
|
||||
}
|
||||
});
|
||||
|
||||
L.NewMarkerControl = L.Control.extend({
|
||||
|
||||
options: {
|
||||
position: 'topleft'
|
||||
},
|
||||
|
||||
onAdd: function (map) {
|
||||
var container = L.DomUtil.create('div', 'leaflet-control leaflet-bar'),
|
||||
link = L.DomUtil.create('a', '', container);
|
||||
|
||||
link.href = '#';
|
||||
link.title = 'Add a new marker';
|
||||
link.innerHTML = '⚫';
|
||||
L.DomEvent.on(link, 'click', L.DomEvent.stop)
|
||||
.on(link, 'click', function () {
|
||||
map.editable.startNewMarker();
|
||||
});
|
||||
|
||||
return container;
|
||||
}
|
||||
});
|
||||
|
||||
L.ExtendMultiControl = L.Control.extend({
|
||||
|
||||
options: {
|
||||
position: 'topleft'
|
||||
},
|
||||
|
||||
onAdd: function (map) {
|
||||
var container = L.DomUtil.create('div', 'leaflet-control leaflet-bar'),
|
||||
link = L.DomUtil.create('a', '', container);
|
||||
|
||||
link.href = '#';
|
||||
link.title = 'Add a new polygon to multi';
|
||||
link.innerHTML = '⧉';
|
||||
L.DomEvent.on(link, 'click', L.DomEvent.stop)
|
||||
.on(link, 'click', function () {
|
||||
map.editable.extendMultiPolygon();
|
||||
});
|
||||
|
||||
var toggle = function (e) {
|
||||
if (e && e.editor && e.editor.feature.multi) {
|
||||
container.style.display = 'block';
|
||||
} else {
|
||||
container.style.display = 'none';
|
||||
}
|
||||
};
|
||||
toggle();
|
||||
|
||||
map.editable.on('editable:editorchanged', toggle);
|
||||
|
||||
return container;
|
||||
}
|
||||
});
|
85
example/index.html
Normal file
85
example/index.html
Normal file
@ -0,0 +1,85 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<title>Leaflet.Editable demo</title>
|
||||
<link rel="stylesheet" href="../node_modules/leaflet/dist/leaflet.css" />
|
||||
<script src="../node_modules/leaflet/dist/leaflet-src.js"></script>
|
||||
<script src="../src/Leaflet.Editable.js"></script>
|
||||
<script src="example.js"></script>
|
||||
|
||||
<style type='text/css'>
|
||||
body { margin:0; padding:0; }
|
||||
#map { position:absolute; top:0; bottom:0; right: 0; left: 0; width:100%; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id='map'></div>
|
||||
|
||||
<script type="text/javascript">
|
||||
var startPoint = [43.1249, 1.254];
|
||||
var map = L.map('map', {attributionControl: false, loadingControl: true}).setView(startPoint, 16),
|
||||
tilelayer = L.tileLayer('http://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png', {maxZoom: 20, attribution: 'Data \u00a9 <a href="http://www.openstreetmap.org/copyright"> OpenStreetMap Contributors </a> Tiles \u00a9 HOT'}).addTo(map);
|
||||
|
||||
map.addControl(new L.NewMarkerControl());
|
||||
map.addControl(new L.NewLineControl());
|
||||
map.addControl(new L.NewPolygonControl());
|
||||
map.addControl(new L.NewHoleControl());
|
||||
map.addControl(new L.ExtendMultiControl());
|
||||
|
||||
var line = L.polyline([
|
||||
[43.1292, 1.256],
|
||||
[43.1295, 1.259],
|
||||
[43.1291, 1.261],
|
||||
]).addTo(map);
|
||||
line.on('dblclick', L.DomEvent.stop).on('dblclick', line.toggleEdit);
|
||||
|
||||
var multi = L.multiPolygon([
|
||||
[
|
||||
[
|
||||
[43.1239, 1.244],
|
||||
[43.123, 1.253],
|
||||
[43.1252, 1.255],
|
||||
[43.1250, 1.251],
|
||||
[43.1239, 1.244]
|
||||
],
|
||||
[
|
||||
[43.124, 1.246],
|
||||
[43.1236, 1.248],
|
||||
[43.12475, 1.250]
|
||||
],
|
||||
[
|
||||
[43.124, 1.251],
|
||||
[43.1236, 1.253],
|
||||
[43.12475, 1.254]
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
[43.1269, 1.246],
|
||||
[43.126, 1.252],
|
||||
[43.1282, 1.255],
|
||||
[43.1280, 1.245],
|
||||
]
|
||||
]
|
||||
]).addTo(map);
|
||||
multi.on('dblclick', L.DomEvent.stop).on('dblclick', multi.toggleEdit);
|
||||
|
||||
var poly = L.polygon([
|
||||
[
|
||||
[43.1239, 1.259],
|
||||
[43.123, 1.263],
|
||||
[43.1252, 1.265],
|
||||
[43.1250, 1.261]
|
||||
],
|
||||
[
|
||||
[43.124, 1.263],
|
||||
[43.1236, 1.261],
|
||||
[43.12475, 1.262]
|
||||
]
|
||||
]).addTo(map);
|
||||
poly.on('dblclick', L.DomEvent.stop).on('dblclick', poly.toggleEdit);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
23
package.json
Normal file
23
package.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "leaflet-editable",
|
||||
"version": "0.0.0",
|
||||
"description": "Make geometries editable in Leaflet",
|
||||
"main": "src/Leaflet.Editable.js",
|
||||
"scripts": {
|
||||
"test": "make test"
|
||||
},
|
||||
"keywords": [
|
||||
"leaflet",
|
||||
"map"
|
||||
],
|
||||
"author": "Yohan Boniface",
|
||||
"license": "WTFPL",
|
||||
"devDependencies": {
|
||||
"chai": "^1.9.1",
|
||||
"happen": "^0.1.3",
|
||||
"mocha": "^1.21.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"leaflet": "^0.7.3"
|
||||
}
|
||||
}
|
950
src/Leaflet.Editable.js
Normal file
950
src/Leaflet.Editable.js
Normal file
@ -0,0 +1,950 @@
|
||||
L.Editable = L.Class.extend({
|
||||
|
||||
statics: {
|
||||
FORWARD: 1,
|
||||
BACKWARD: -1
|
||||
},
|
||||
|
||||
includes: L.Mixin.Events,
|
||||
|
||||
options: {
|
||||
zIndex: 10000,
|
||||
polygonClass: L.Polygon,
|
||||
polylineClass: L.Polyline,
|
||||
markerClass: L.Marker
|
||||
},
|
||||
|
||||
initialize: function (map) {
|
||||
this.map = map;
|
||||
this.editLayer = new L.LayerGroup().addTo(map);
|
||||
this.newClickHandler = L.marker(this.map.getCenter(), {
|
||||
icon: L.Browser.touch ? new L.Editable.TouchDivIcon() : new L.Editable.DivIcon(),
|
||||
opacity: 0,
|
||||
// zIndexOffset: this.options.zIndex
|
||||
});
|
||||
this.forwardLineGuide = this.createLineGuide();
|
||||
this.backwardLineGuide = this.createLineGuide();
|
||||
|
||||
var activeEditor = null,
|
||||
self = this;
|
||||
try {
|
||||
Object.defineProperty(this, 'activeEditor', {
|
||||
get: function () {
|
||||
return activeEditor;
|
||||
},
|
||||
set: function (editor) {
|
||||
var oldEditor = activeEditor; // prevent looping with editor.disable()
|
||||
activeEditor = editor;
|
||||
if (oldEditor && oldEditor != editor) {
|
||||
if (editor && editor.feature.multi && oldEditor.feature.multi === editor.feature.multi) {
|
||||
oldEditor.setSecondary();
|
||||
} else {
|
||||
oldEditor.disable();
|
||||
}
|
||||
if (oldEditor.feature.multi && (!editor || oldEditor.feature.multi !== editor.feature.multi)) {
|
||||
oldEditor.feature.multi.endEdit();
|
||||
}
|
||||
}
|
||||
self.fire('editable:editorchanged', {editor: editor});
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
// Certainly IE8, which has a limited version of defineProperty
|
||||
}
|
||||
},
|
||||
|
||||
createLineGuide: function () {
|
||||
return L.polyline([], {dashArray: '5,10', weight: 1});
|
||||
},
|
||||
|
||||
moveForwardLineGuide: function (latlng) {
|
||||
if (this.forwardLineGuide._latlngs.length) {
|
||||
this.forwardLineGuide._latlngs[1] = latlng;
|
||||
this.forwardLineGuide.redraw();
|
||||
}
|
||||
},
|
||||
|
||||
moveBackwardLineGuide: function (latlng) {
|
||||
if (this.backwardLineGuide._latlngs.length) {
|
||||
this.backwardLineGuide._latlngs[1] = latlng;
|
||||
this.backwardLineGuide.redraw();
|
||||
}
|
||||
},
|
||||
|
||||
newPointForward: function (latlng) {
|
||||
this.activeEditor.addLatLng(latlng);
|
||||
this.anchorForwardLineGuide(latlng);
|
||||
},
|
||||
|
||||
newPointBackward: function (latlng) {
|
||||
this.activeEditor.addLatLng(latlng);
|
||||
this.anchorBackwardLineGuide(latlng);
|
||||
},
|
||||
|
||||
anchorForwardLineGuide: function (latlng) {
|
||||
this.forwardLineGuide._latlngs[0] = latlng;
|
||||
this.forwardLineGuide.redraw();
|
||||
},
|
||||
|
||||
anchorBackwardLineGuide: function (latlng) {
|
||||
this.backwardLineGuide._latlngs[0] = latlng;
|
||||
this.backwardLineGuide.redraw();
|
||||
},
|
||||
|
||||
attachForwardLineGuide: function () {
|
||||
this.editLayer.addLayer(this.forwardLineGuide);
|
||||
},
|
||||
|
||||
attachBackwardLineGuide: function () {
|
||||
this.editLayer.addLayer(this.backwardLineGuide);
|
||||
},
|
||||
|
||||
detachForwardLineGuide: function () {
|
||||
this.forwardLineGuide._latlngs = [];
|
||||
this.editLayer.removeLayer(this.forwardLineGuide);
|
||||
},
|
||||
|
||||
detachBackwardLineGuide: function () {
|
||||
this.backwardLineGuide._latlngs = [];
|
||||
this.editLayer.removeLayer(this.backwardLineGuide);
|
||||
},
|
||||
|
||||
onMouseMove: function (e) {
|
||||
if (this.activeEditor) {
|
||||
this.map.editable.newClickHandler.setLatLng(e.latlng);
|
||||
this.activeEditor.onMouseMove(e);
|
||||
}
|
||||
},
|
||||
|
||||
onTouch: function (e) {
|
||||
this.onMouseMove(e);
|
||||
if (this.activeEditor && this.activeEditor.drawing) {
|
||||
this.newClickHandler._fireMouseEvent(e);
|
||||
}
|
||||
},
|
||||
|
||||
startNewLine: function () {
|
||||
var line = this.createLine([]).connectToMap(this.map),
|
||||
editor = line.edit();
|
||||
editor.startDrawingForward();
|
||||
return line;
|
||||
},
|
||||
|
||||
startNewPolygon: function () {
|
||||
var polygon = this.createPolygon([]).connectToMap(this.map),
|
||||
editor = polygon.edit();
|
||||
editor.startDrawingForward();
|
||||
return polygon;
|
||||
},
|
||||
|
||||
startNewHole: function () {
|
||||
if (!this.activeEditor || !this.activeEditor instanceof L.Editable.PolygonEditor) return;
|
||||
this.activeEditor.newHole();
|
||||
},
|
||||
|
||||
extendMultiPolygon: function () {
|
||||
if (!this.multi) return;
|
||||
var polygon = this.createPolygon([]);
|
||||
this.multi.addLayer(polygon);
|
||||
polygon.multi = this.multi;
|
||||
var editor = polygon.edit();
|
||||
this.multi.setPrimary(polygon);
|
||||
editor.startDrawingForward();
|
||||
return polygon;
|
||||
},
|
||||
|
||||
startNewMarker: function (latlng) {
|
||||
latlng = latlng || this.map.getCenter();
|
||||
var marker = this.createMarker(latlng).connectToMap(this.map),
|
||||
editor = marker.edit();
|
||||
editor.startDrawing();
|
||||
return marker;
|
||||
},
|
||||
|
||||
createLine: function (latlngs) {
|
||||
return new this.options.polylineClass(latlngs);
|
||||
},
|
||||
|
||||
createPolygon: function (latlngs) {
|
||||
return new this.options.polygonClass(latlngs);
|
||||
},
|
||||
|
||||
createMarker: function (latlng) {
|
||||
return new this.options.markerClass(latlng);
|
||||
},
|
||||
|
||||
addNewClickHandler: function () {
|
||||
if (!this.activeEditor) return;
|
||||
this.editLayer.addLayer(this.newClickHandler);
|
||||
this.newClickHandler.on('click', function (e) {
|
||||
this.onNewClickHandlerClicked(e);
|
||||
}, this.activeEditor);
|
||||
if (L.Browser.touch) this.map.on('click', this.onTouch, this);
|
||||
},
|
||||
|
||||
removeNewClickHandler: function () {
|
||||
this.editLayer.removeLayer(this.newClickHandler);
|
||||
this.newClickHandler.off();
|
||||
if (L.Browser.touch) this.map.off('click', this.onTouch, this);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
L.Map.addInitHook(function () {
|
||||
|
||||
this.whenReady(function () {
|
||||
this.editable = new L.Editable(this);
|
||||
this.on('mousemove touchmove', this.editable.onMouseMove, this.editable);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
L.Editable.DivIcon = L.DivIcon.extend({
|
||||
|
||||
options: {
|
||||
iconSize: new L.Point(8, 8),
|
||||
className: 'leaflet-div-icon leaflet-editing-icon'
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
L.Editable.TouchDivIcon = L.Editable.DivIcon.extend({
|
||||
|
||||
options: {
|
||||
iconSize: new L.Point(20, 20)
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
L.Editable.VertexMarker = L.Marker.extend({
|
||||
|
||||
options: {
|
||||
draggable: true,
|
||||
riseOnOver: true,
|
||||
icon: L.Browser.touch ? new L.Editable.TouchDivIcon() : new L.Editable.DivIcon(),
|
||||
zIndex: 10001
|
||||
},
|
||||
|
||||
initialize: function (latlng, latlngs, editor, options) {
|
||||
this.latlng = latlng;
|
||||
this.latlngs = latlngs;
|
||||
this.editor = editor;
|
||||
L.setOptions(this, options);
|
||||
L.Marker.prototype.initialize.call(this, latlng);
|
||||
if (this.editor.secondary) this.setSecondary();
|
||||
this.latlng.__vertex = this;
|
||||
this.editor.editLayer.addLayer(this);
|
||||
},
|
||||
|
||||
setSecondary: function () {
|
||||
this.setOpacity(0.3);
|
||||
},
|
||||
|
||||
setPrimary: function () {
|
||||
this.setOpacity(1);
|
||||
},
|
||||
|
||||
onAdd: function (map) {
|
||||
L.Marker.prototype.onAdd.call(this, map);
|
||||
L.DomEvent.on(this.dragging._draggable, 'drag', this.onDrag, this);
|
||||
this.on('click', this.onClick);
|
||||
this.on('mousedown touchstart', this.onMouseDown);
|
||||
this.addMiddleMarkers();
|
||||
},
|
||||
|
||||
onDrag: function (e) {
|
||||
var iconPos = L.DomUtil.getPosition(this._icon),
|
||||
latlng = this._map.layerPointToLatLng(iconPos);
|
||||
this.latlng.lat = latlng.lat;
|
||||
this.latlng.lng = latlng.lng;
|
||||
this.editor.feature.redraw();
|
||||
if (this.middleMarker) {
|
||||
this.middleMarker.updateLatLng();
|
||||
}
|
||||
var next = this.getNext();
|
||||
if (next && next.middleMarker) {
|
||||
next.middleMarker.updateLatLng();
|
||||
}
|
||||
},
|
||||
|
||||
onClick: function (e) {
|
||||
this.editor.onVertexMarkerClick(e, this);
|
||||
},
|
||||
|
||||
onMouseDown: function (e) {
|
||||
if (this.editor.secondary) {
|
||||
this.editor.setPrimary();
|
||||
}
|
||||
},
|
||||
|
||||
remove: function () {
|
||||
var next = this.getNext();
|
||||
if (this.middleMarker) this.middleMarker.remove();
|
||||
delete this.latlng.__vertex;
|
||||
this.latlngs.splice(this.latlngs.indexOf(this.latlng), 1);
|
||||
if (next) next.resetMiddleMarker();
|
||||
},
|
||||
|
||||
getPosition: function () {
|
||||
return this.latlngs.indexOf(this.latlng);
|
||||
},
|
||||
|
||||
getLastIndex: function () {
|
||||
return this.latlngs.length - 1;
|
||||
},
|
||||
|
||||
getPrevious: function () {
|
||||
if (this.latlngs.length < 2) return;
|
||||
var position = this.getPosition(),
|
||||
previousPosition = position - 1;
|
||||
if (position === 0 && this.editor.CLOSED) previousPosition = this.getLastIndex();
|
||||
var previous = this.latlngs[previousPosition];
|
||||
if (previous) return previous.__vertex;
|
||||
},
|
||||
|
||||
getNext: function () {
|
||||
if (this.latlngs.length < 2) return;
|
||||
var position = this.getPosition(),
|
||||
nextPosition = position + 1;
|
||||
if (position === this.getLastIndex() && this.editor.CLOSED) nextPosition = 0;
|
||||
var next = this.latlngs[nextPosition];
|
||||
if (next) return next.__vertex;
|
||||
},
|
||||
|
||||
addMiddleMarker: function (previous) {
|
||||
previous = previous || this.getPrevious();
|
||||
if (previous && !this.middleMarker) this.middleMarker = this.editor.addMiddleMarker(previous, this, this.latlngs, this.editor);
|
||||
},
|
||||
|
||||
addMiddleMarkers: function () {
|
||||
var previous = this.getPrevious();
|
||||
if (previous) {
|
||||
this.addMiddleMarker(previous);
|
||||
}
|
||||
var next = this.getNext();
|
||||
if (next) {
|
||||
next.resetMiddleMarker();
|
||||
}
|
||||
},
|
||||
|
||||
resetMiddleMarker: function () {
|
||||
if (this.middleMarker) this.middleMarker.remove();
|
||||
this.addMiddleMarker();
|
||||
},
|
||||
|
||||
_initInteraction: function () {
|
||||
L.Marker.prototype._initInteraction.call(this);
|
||||
L.DomEvent.on(this._icon, 'touchstart', function (e) {this._fireMouseEvent(e);}, this);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
L.Editable.mergeOptions({
|
||||
vertexMarkerClass: L.Editable.VertexMarker
|
||||
});
|
||||
|
||||
L.Editable.MiddleMarker = L.Marker.extend({
|
||||
|
||||
options: {
|
||||
icon: L.Browser.touch ? new L.Editable.TouchDivIcon() : new L.Editable.DivIcon(),
|
||||
zIndex: 10000,
|
||||
opacity: 0.5
|
||||
},
|
||||
|
||||
initialize: function (left, right, latlngs, editor, options) {
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
this.editor = editor;
|
||||
this.latlngs = latlngs;
|
||||
L.Marker.prototype.initialize.call(this, this.computeLatLng());
|
||||
if (this.editor.secondary) this.setSecondary();
|
||||
this.editor.editLayer.addLayer(this);
|
||||
},
|
||||
|
||||
setSecondary: function () {
|
||||
this.setOpacity(0.2);
|
||||
},
|
||||
|
||||
setPrimary: function () {
|
||||
this.setOpacity(this.options.opacity);
|
||||
},
|
||||
|
||||
updateLatLng: function () {
|
||||
this.setLatLng(this.computeLatLng());
|
||||
},
|
||||
|
||||
computeLatLng: function () {
|
||||
var lat = (this.left.latlng.lat + this.right.latlng.lat) / 2,
|
||||
lng = (this.left.latlng.lng + this.right.latlng.lng) / 2;
|
||||
return [lat, lng];
|
||||
},
|
||||
|
||||
onAdd: function (map) {
|
||||
L.Marker.prototype.onAdd.call(this, map);
|
||||
this.on('mousedown touchstart', this.onMouseDown);
|
||||
},
|
||||
|
||||
onMouseDown: function (e) {
|
||||
this.latlngs.splice(this.index(), 0, e.latlng);
|
||||
this.editor.feature.redraw();
|
||||
this.editor.setPrimary();
|
||||
this.remove();
|
||||
var marker = this.editor.addVertexMarker(e.latlng, this.latlngs);
|
||||
marker.dragging._draggable._onDown(e.originalEvent); // Transfer ongoing dragging to real marker
|
||||
},
|
||||
|
||||
remove: function () {
|
||||
this.editor.editLayer.removeLayer(this);
|
||||
delete this.right.middleMarker;
|
||||
},
|
||||
|
||||
index: function () {
|
||||
return this.latlngs.indexOf(this.right.latlng);
|
||||
},
|
||||
|
||||
_initInteraction: function () {
|
||||
L.Marker.prototype._initInteraction.call(this);
|
||||
L.DomEvent.on(this._icon, 'touchstart', function (e) {this._fireMouseEvent(e);}, this);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
L.Editable.mergeOptions({
|
||||
middleMarkerClass: L.Editable.MiddleMarker
|
||||
});
|
||||
|
||||
L.Editable.BaseEditor = L.Class.extend({
|
||||
|
||||
initialize: function (map, feature, options) {
|
||||
this.map = map;
|
||||
this.feature = feature;
|
||||
this.feature.editor = this;
|
||||
this.editLayer = new L.LayerGroup();
|
||||
|
||||
var self = this;
|
||||
try {
|
||||
Object.defineProperty(this, 'active', {
|
||||
get: function () {
|
||||
return self.map.editable.activeEditor === self;
|
||||
},
|
||||
set: function (status) {
|
||||
if (status && self.map.editable.activeEditor !== self) {
|
||||
self.map.editable.activeEditor = self;
|
||||
}
|
||||
if (!status && self.map.editable.activeEditor === self) {
|
||||
self.map.editable.activeEditor = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
// Certainly IE8, which has a limited version of defineProperty
|
||||
}
|
||||
},
|
||||
|
||||
enable: function () {
|
||||
this.map.editable.editLayer.addLayer(this.editLayer);
|
||||
this.onEnable();
|
||||
return this;
|
||||
},
|
||||
|
||||
disable: function () {
|
||||
this.editLayer.clearLayers();
|
||||
this.map.editable.editLayer.removeLayer(this.editLayer);
|
||||
this.onDisable();
|
||||
return this;
|
||||
},
|
||||
|
||||
onEnable: function () {
|
||||
this.feature.fire('editable:enable', {layer: this.feature});
|
||||
},
|
||||
|
||||
onDisable: function () {
|
||||
this.feature.fire('editable:disable', {layer: this.feature});
|
||||
},
|
||||
|
||||
onEditing: function () {
|
||||
this.feature.fire('editable:editing', {layer: this.feature});
|
||||
},
|
||||
|
||||
onEdited: function () {
|
||||
this.feature.fire('editable:edited', {layer: this.feature});
|
||||
},
|
||||
|
||||
startDrawing: function () {
|
||||
if (!this.drawing) this.drawing = L.Editable.FORWARD;
|
||||
this.map.editable.addNewClickHandler();
|
||||
this.onEditing();
|
||||
},
|
||||
|
||||
finishDrawing: function () {
|
||||
this.onEdited();
|
||||
this.drawing = false;
|
||||
this.map.editable.removeNewClickHandler();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
L.Editable.MarkerEditor = L.Editable.BaseEditor.extend({
|
||||
|
||||
enable: function () {
|
||||
L.Editable.BaseEditor.prototype.enable.call(this);
|
||||
this.active = true;
|
||||
this.feature.dragging.enable();
|
||||
this.feature.on('dragstart', this.onEditing, this);
|
||||
return this;
|
||||
},
|
||||
|
||||
disable: function () {
|
||||
L.Editable.BaseEditor.prototype.disable.call(this);
|
||||
this.active = false;
|
||||
this.feature.dragging.disable();
|
||||
this.feature.off('dragstart', this.onEditing, this);
|
||||
return this;
|
||||
},
|
||||
|
||||
onMouseMove: function (e) {
|
||||
if (this.drawing) {
|
||||
this.feature.setLatLng(e.latlng);
|
||||
this.map.editable.newClickHandler._bringToFront();
|
||||
}
|
||||
},
|
||||
|
||||
onNewClickHandlerClicked: function (e) {
|
||||
if (this.checkAddConstraints && !this.checkAddConstraints(e.latlng)) {
|
||||
return;
|
||||
}
|
||||
this.finishDrawing();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
L.Editable.PathEditor = L.Editable.BaseEditor.extend({
|
||||
|
||||
CLOSED: false,
|
||||
|
||||
enable: function (secondary) {
|
||||
L.Editable.BaseEditor.prototype.enable.call(this);
|
||||
this.secondary = secondary;
|
||||
if (this.feature) {
|
||||
this.initVertexMarkers();
|
||||
}
|
||||
if (!this.secondary) this.active = true;
|
||||
return this;
|
||||
},
|
||||
|
||||
disable: function () {
|
||||
L.Editable.BaseEditor.prototype.disable.call(this);
|
||||
if (!this.secondary) this.active = false;
|
||||
},
|
||||
|
||||
setPrimary: function () {
|
||||
if (this.feature.multi) {
|
||||
this.feature.multi.setSecondary(this.feature);
|
||||
}
|
||||
delete this.secondary;
|
||||
this.active = true;
|
||||
this.editLayer.eachLayer(function (layer) {
|
||||
layer.setPrimary();
|
||||
});
|
||||
},
|
||||
|
||||
setSecondary: function () {
|
||||
this.secondary = true;
|
||||
this.editLayer.eachLayer(function (layer) {
|
||||
if (layer.setSecondary) layer.setSecondary();
|
||||
});
|
||||
},
|
||||
|
||||
initVertexMarkers: function () {
|
||||
// groups can be only latlngs (for polyline or symple polygon,
|
||||
// or latlngs plus many holes, in case of a complex polygon)
|
||||
var latLngGroups = this.getLatLngsGroups();
|
||||
for (var i = 0; i < latLngGroups.length; i++) {
|
||||
this.addVertexMarkers(latLngGroups[i]);
|
||||
}
|
||||
},
|
||||
|
||||
addVertexMarker: function (latlng, latlngs) {
|
||||
return new this.map.editable.options.vertexMarkerClass(latlng, latlngs, this);
|
||||
},
|
||||
|
||||
addVertexMarkers: function (latlngs) {
|
||||
for (var i = 0; i < latlngs.length; i++) {
|
||||
this.addVertexMarker(latlngs[i], latlngs);
|
||||
}
|
||||
},
|
||||
|
||||
onVertexMarkerClick: function (e, vertex) {
|
||||
var position = vertex.getPosition();
|
||||
if (e.originalEvent.ctrlKey) {
|
||||
this.onVertexMarkerCtrlClick(e, vertex, position);
|
||||
} else if (e.originalEvent.altKey) {
|
||||
this.onVertexMarkerAltClick(e, vertex, position);
|
||||
} else if (e.originalEvent.shiftKey) {
|
||||
this.onVertexMarkerShiftClick(e, vertex, position);
|
||||
} else if (position >= 1 && position === vertex.getLastIndex() && this.drawing === L.Editable.FORWARD) {
|
||||
this.finishDrawing();
|
||||
} else if (position === 0 && this.drawing === L.Editable.BACKWARD && this.activeLatLngs.length >= 2) {
|
||||
this.finishDrawing();
|
||||
} else {
|
||||
this.onVertexRawMarkerClick(e, vertex, position);
|
||||
}
|
||||
},
|
||||
|
||||
onVertexRawMarkerClick: function (e, vertex, position) {
|
||||
vertex.remove();
|
||||
this.editLayer.removeLayer(vertex);
|
||||
this.feature.redraw();
|
||||
},
|
||||
|
||||
onVertexMarkerCtrlClick: function (e, vertex, position) {
|
||||
this.feature.fire('editable:vertexctrlclick', {
|
||||
originalEvent: e.originalEvent,
|
||||
latlng: e.latlng,
|
||||
vertex: vertex,
|
||||
position: position
|
||||
});
|
||||
},
|
||||
|
||||
onVertexMarkerShiftClick: function (e, vertex, position) {
|
||||
this.feature.fire('editable:vertexshiftclick', {
|
||||
originalEvent: e.originalEvent,
|
||||
latlng: e.latlng,
|
||||
vertex: vertex,
|
||||
position: position
|
||||
});
|
||||
},
|
||||
|
||||
onVertexMarkerAltClick: function (e, vertex, position) {
|
||||
this.feature.fire('editable:vertexaltclick', {
|
||||
originalEvent: e.originalEvent,
|
||||
latlng: e.latlng,
|
||||
vertex: vertex,
|
||||
position: position
|
||||
});
|
||||
},
|
||||
|
||||
addMiddleMarker: function (left, right, latlngs) {
|
||||
return new this.map.editable.options.middleMarkerClass(left, right, latlngs, this);
|
||||
},
|
||||
|
||||
startDrawingForward: function () {
|
||||
this.startDrawing();
|
||||
this.map.editable.attachForwardLineGuide();
|
||||
},
|
||||
|
||||
finishDrawing: function () {
|
||||
L.Editable.BaseEditor.prototype.finishDrawing.call(this);
|
||||
this.map.editable.detachForwardLineGuide();
|
||||
this.map.editable.detachBackwardLineGuide();
|
||||
this.unsetActiveLatLngs();
|
||||
delete this.checkConstraints;
|
||||
},
|
||||
|
||||
addLatLng: function (latlng) {
|
||||
this.setActiveLatLngs(latlng);
|
||||
if (this.drawing === L.Editable.FORWARD) this.activeLatLngs.push(latlng);
|
||||
else this.activeLatLngs.unshift(latlng);
|
||||
this.feature.redraw();
|
||||
this.addVertexMarker(latlng, this.activeLatLngs);
|
||||
},
|
||||
|
||||
onNewClickHandlerClicked: function (e) {
|
||||
if (this.checkAddConstraints && !this.checkAddConstraints(e.latlng)) {
|
||||
return;
|
||||
}
|
||||
if (this.drawing === L.Editable.FORWARD) this.map.editable.newPointForward(e.latlng);
|
||||
else this.map.editable.newPointBackward(e.latlng);
|
||||
if (!this.map.editable.backwardLineGuide._latlngs[0]) {
|
||||
this.map.editable.anchorBackwardLineGuide(e.latlng);
|
||||
}
|
||||
this.feature.fire('editable:newclick', e);
|
||||
},
|
||||
|
||||
setActiveLatLngs: function (latlng) {
|
||||
if (!this.activeLatLngs) {
|
||||
this.activeLatLngs = this.getLatLngs(latlng);
|
||||
}
|
||||
},
|
||||
|
||||
unsetActiveLatLngs: function () {
|
||||
delete this.activeLatLngs;
|
||||
},
|
||||
|
||||
onMouseMove: function (e) {
|
||||
if (this.drawing) {
|
||||
this.map.editable.moveForwardLineGuide(e.latlng);
|
||||
this.map.editable.moveBackwardLineGuide(e.latlng);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
L.Editable.PolylineEditor = L.Editable.PathEditor.extend({
|
||||
|
||||
getLatLngsGroups: function () {
|
||||
return [this.getLatLngs()];
|
||||
},
|
||||
|
||||
getLatLngs: function () {
|
||||
return this.feature.getLatLngs();
|
||||
},
|
||||
|
||||
startDrawingBackward: function () {
|
||||
this.drawing = L.Editable.BACKWARD;
|
||||
this.startDrawing();
|
||||
this.map.editable.attachBackwardLineGuide();
|
||||
},
|
||||
|
||||
continueBackward: function () {
|
||||
this.map.editable.anchorBackwardLineGuide(this.getFirstLatLng());
|
||||
this.startDrawingBackward();
|
||||
},
|
||||
|
||||
continueForward: function () {
|
||||
this.map.editable.anchorForwardLineGuide(this.getLastLatLng());
|
||||
this.startDrawingForward();
|
||||
},
|
||||
|
||||
getLastLatLng: function () {
|
||||
return this.getLatLngs()[this.getLatLngs().length - 1];
|
||||
},
|
||||
|
||||
getFirstLatLng: function () {
|
||||
return this.getLatLngs()[0];
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
L.Editable.PolygonEditor = L.Editable.PathEditor.extend({
|
||||
|
||||
CLOSED: true,
|
||||
|
||||
getLatLngsGroups: function () {
|
||||
var groups = [this.feature._latlngs];
|
||||
if (this.feature._holes) {
|
||||
for (var i = 0; i < this.feature._holes.length; i++) {
|
||||
groups.push(this.feature._holes[i]);
|
||||
}
|
||||
}
|
||||
return groups;
|
||||
},
|
||||
|
||||
startDrawingForward: function () {
|
||||
L.Editable.PathEditor.prototype.startDrawingForward.call(this);
|
||||
this.map.editable.attachBackwardLineGuide();
|
||||
},
|
||||
|
||||
finishDrawing: function () {
|
||||
L.Editable.PathEditor.prototype.finishDrawing.call(this);
|
||||
this.map.editable.detachBackwardLineGuide();
|
||||
},
|
||||
|
||||
getLatLngs: function (latlng) {
|
||||
if (latlng) {
|
||||
var p = this.map.latLngToLayerPoint(latlng);
|
||||
if (this.feature._latlngs && this.feature._holes && this.feature._containsPoint(p)) {
|
||||
return this.addNewEmptyHole();
|
||||
}
|
||||
}
|
||||
return this.feature._latlngs;
|
||||
},
|
||||
|
||||
addNewEmptyHole: function () {
|
||||
var holes = Array();
|
||||
if (!this.feature._holes) {
|
||||
this.feature._holes = [];
|
||||
}
|
||||
this.feature._holes.push(holes);
|
||||
return holes;
|
||||
},
|
||||
|
||||
prepareForNewHole: function () {
|
||||
this.activeLatLngs = this.addNewEmptyHole();
|
||||
this.checkAddConstraints = this.checkContains;
|
||||
},
|
||||
|
||||
newHole: function () {
|
||||
this.prepareForNewHole();
|
||||
this.startDrawingForward();
|
||||
},
|
||||
|
||||
checkContains: function (latlng) {
|
||||
return this.feature._containsPoint(this.map.latLngToLayerPoint(latlng));
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
L.Editable.mergeOptions({
|
||||
polylineEditorClass: L.Editable.PolylineEditor
|
||||
});
|
||||
|
||||
L.Editable.mergeOptions({
|
||||
polygonEditorClass: L.Editable.PolygonEditor
|
||||
});
|
||||
|
||||
L.Editable.mergeOptions({
|
||||
markerEditorClass: L.Editable.MarkerEditor
|
||||
});
|
||||
|
||||
var EditableMixin = {
|
||||
|
||||
createEditor: function (map) {
|
||||
map = map || this._map;
|
||||
var Klass = this.options.editorClass || this.getEditorClass(map);
|
||||
return new Klass(map, this);
|
||||
},
|
||||
|
||||
edit: function (secondary) {
|
||||
return this.createEditor().enable(secondary);
|
||||
},
|
||||
|
||||
endEdit: function () {
|
||||
if (this.editor) {
|
||||
this.editor.disable();
|
||||
delete this.editor;
|
||||
}
|
||||
},
|
||||
|
||||
toggleEdit: function () {
|
||||
if (this.editor) {
|
||||
this.endEdit();
|
||||
} else {
|
||||
this.edit();
|
||||
}
|
||||
},
|
||||
|
||||
connectToMap: function (map) {
|
||||
return this.addTo(map);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
L.Polyline.include(EditableMixin);
|
||||
L.Polygon.include(EditableMixin);
|
||||
L.Marker.include(EditableMixin);
|
||||
|
||||
L.Polyline.include({
|
||||
|
||||
_containsPoint: function (p, closed) { // Copy-pasted from Leaflet
|
||||
var i, j, k, len, len2, dist, part,
|
||||
w = this.options.weight / 2;
|
||||
|
||||
if (L.Browser.touch) {
|
||||
w += 10; // polyline click tolerance on touch devices
|
||||
}
|
||||
|
||||
for (i = 0, len = this._parts.length; i < len; i++) {
|
||||
part = this._parts[i];
|
||||
for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
|
||||
if (!closed && (j === 0)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
dist = L.LineUtil.pointToSegmentDistance(p, part[k], part[j]);
|
||||
|
||||
if (dist <= w) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
getEditorClass: function (map) {
|
||||
return map.editable.options.polylineEditorClass;
|
||||
}
|
||||
|
||||
});
|
||||
L.Polygon.include({
|
||||
|
||||
_containsPoint: function (p) { // Copy-pasted from Leaflet
|
||||
var inside = false,
|
||||
part, p1, p2,
|
||||
i, j, k,
|
||||
len, len2;
|
||||
|
||||
// TODO optimization: check if within bounds first
|
||||
|
||||
if (L.Polyline.prototype._containsPoint.call(this, p, true)) {
|
||||
// click on polygon border
|
||||
return true;
|
||||
}
|
||||
|
||||
// ray casting algorithm for detecting if point is in polygon
|
||||
|
||||
for (i = 0, len = this._parts.length; i < len; i++) {
|
||||
part = this._parts[i];
|
||||
|
||||
for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
|
||||
p1 = part[j];
|
||||
p2 = part[k];
|
||||
|
||||
if (((p1.y > p.y) !== (p2.y > p.y)) &&
|
||||
(p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) {
|
||||
inside = !inside;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return inside;
|
||||
},
|
||||
|
||||
getEditorClass: function (map) {
|
||||
return map.editable.options.polygonEditorClass;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
L.Marker.include({
|
||||
|
||||
getEditorClass: function (map) {
|
||||
return map.editable.options.markerEditorClass;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
var MultiEditableMixin = {
|
||||
|
||||
edit: function (e) {
|
||||
this.eachLayer(function(layer) {
|
||||
layer.multi = this;
|
||||
layer.endEdit();
|
||||
layer.edit(e.layer !== layer);
|
||||
}, this);
|
||||
this._map.editable.multi = this;
|
||||
},
|
||||
|
||||
endEdit: function () {
|
||||
this.eachLayer(function(layer) {
|
||||
layer.endEdit();
|
||||
});
|
||||
},
|
||||
|
||||
toggleEdit: function (e) {
|
||||
if (!e.layer.editor || e.layer.editor.secondary) {
|
||||
this.edit(e);
|
||||
} else {
|
||||
this.endEdit();
|
||||
}
|
||||
},
|
||||
|
||||
setPrimary: function (primary) {
|
||||
this.eachLayer(function (layer) {
|
||||
if (layer === primary) layer.editor.setPrimary();
|
||||
else layer.editor.setSecondary();
|
||||
});
|
||||
},
|
||||
|
||||
setSecondary: function (except) {
|
||||
this.eachLayer(function (layer) {
|
||||
if (layer !== except) layer.editor.setSecondary();
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
L.MultiPolygon.include(MultiEditableMixin);
|
||||
L.MultiPolyline.include(MultiEditableMixin);
|
17
test/Editable.js
Normal file
17
test/Editable.js
Normal file
@ -0,0 +1,17 @@
|
||||
describe('L.Editable', function() {
|
||||
|
||||
before(function () {
|
||||
this.map = map;
|
||||
});
|
||||
after(function () {
|
||||
});
|
||||
|
||||
describe('#init', function() {
|
||||
|
||||
it('should be initialized', function() {
|
||||
assert.ok(this.map.editable);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
67
test/MarkerEditor.js
Normal file
67
test/MarkerEditor.js
Normal file
@ -0,0 +1,67 @@
|
||||
describe('L.MarkerEditor', function() {
|
||||
var mouse, marker;
|
||||
|
||||
before(function () {
|
||||
this.map = map;
|
||||
});
|
||||
after(function () {
|
||||
marker.endEdit();
|
||||
this.map.removeLayer(marker);
|
||||
});
|
||||
|
||||
describe('#startNewPolygon()', function() {
|
||||
|
||||
it('should create feature and editor', function() {
|
||||
marker = this.map.editable.startNewMarker();
|
||||
assert.ok(this.map.editable.activeEditor);
|
||||
assert.ok(marker);
|
||||
});
|
||||
|
||||
it('should update marker position on mousemove', function () {
|
||||
happen.at('mousemove', 200, 200);
|
||||
var before = marker._latlng;
|
||||
happen.at('mousemove', 300, 300);
|
||||
assert.notEqual(before, marker._latlng);
|
||||
});
|
||||
|
||||
it('should set latlng on first click', function () {
|
||||
happen.at('click', 300, 300);
|
||||
var before = marker._latlng;
|
||||
happen.at('mousemove', 400, 400);
|
||||
assert.equal(before, marker._latlng);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#disable()', function () {
|
||||
|
||||
it('should stop editing on disable() call', function () {
|
||||
marker.endEdit();
|
||||
assert.notOk(this.map.editable.activeEditor);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#enable()', function () {
|
||||
|
||||
it('should start editing on enable() call', function () {
|
||||
marker.edit();
|
||||
assert.ok(this.map.editable.activeEditor);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#drag()', function () {
|
||||
|
||||
it('should update latlng on marker drag', function (done) {
|
||||
var before = marker._latlng.lat,
|
||||
self = this;
|
||||
happen.drag(300, 299, 350, 350, function () {
|
||||
assert.notEqual(before, marker._latlng.lat);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
137
test/PolygonEditor.js
Normal file
137
test/PolygonEditor.js
Normal file
@ -0,0 +1,137 @@
|
||||
describe('L.PolygonEditor', function() {
|
||||
var mouse, polygon;
|
||||
|
||||
before(function () {
|
||||
this.map = map;
|
||||
});
|
||||
after(function () {
|
||||
polygon.editor.disable();
|
||||
this.map.removeLayer(polygon);
|
||||
});
|
||||
|
||||
describe('#startNewPolygon()', function() {
|
||||
|
||||
it('should create feature and editor', function() {
|
||||
this.map.editable.startNewPolygon();
|
||||
assert.ok(this.map.editable.activeEditor);
|
||||
polygon = this.map.editable.activeEditor.feature;
|
||||
assert.ok(polygon);
|
||||
assert.notOk(this.map.editable.activeEditor.feature._latlngs.length);
|
||||
});
|
||||
|
||||
it('should create latlng on click', function () {
|
||||
happen.at('mousemove', 100, 150);
|
||||
happen.at('click', 100, 150);
|
||||
assert.equal(this.map.editable.activeEditor.feature._latlngs.length, 1);
|
||||
happen.at('mousemove', 200, 350);
|
||||
happen.at('click', 200, 350);
|
||||
assert.equal(this.map.editable.activeEditor.feature._latlngs.length, 2);
|
||||
happen.at('mousemove', 300, 250);
|
||||
happen.at('click', 300, 250);
|
||||
assert.equal(this.map.editable.activeEditor.feature._latlngs.length, 3);
|
||||
happen.at('mousemove', 300, 150);
|
||||
happen.at('click', 300, 150);
|
||||
assert.equal(this.map.editable.activeEditor.feature._latlngs.length, 4);
|
||||
});
|
||||
|
||||
it('should finish shape on last point click', function () {
|
||||
happen.at('click', 300, 150);
|
||||
assert.equal(this.map.editable.activeEditor.feature._latlngs.length, 4);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#disable()', function () {
|
||||
|
||||
it('should stop editing on disable() call', function () {
|
||||
polygon.endEdit();
|
||||
assert.notOk(this.map.editable.activeEditor);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#enable()', function () {
|
||||
|
||||
it('should start editing on enable() call', function () {
|
||||
polygon.edit();
|
||||
assert.ok(this.map.editable.activeEditor);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#dragVertex()', function () {
|
||||
|
||||
it('should update latlng on vertex drag', function (done) {
|
||||
var before = this.map.editable.activeEditor.feature._latlngs[2].lat,
|
||||
self = this;
|
||||
happen.drag(300, 250, 310, 260, function () {
|
||||
assert.notEqual(before, self.map.editable.activeEditor.feature._latlngs[2].lat);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#deleteVertex()', function () {
|
||||
|
||||
it('should delete latlng on vertex click', function () {
|
||||
happen.at('click', 200, 350);
|
||||
assert.equal(this.map.editable.activeEditor.feature._latlngs.length, 3);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#dragMiddleMarker()', function (done) {
|
||||
|
||||
it('should insert new latlng on middle marker click', function (done) {
|
||||
var first = this.map.editable.activeEditor.feature._latlngs[0],
|
||||
second = this.map.editable.activeEditor.feature._latlngs[1],
|
||||
self = this,
|
||||
fromX = (100 + 310) / 2,
|
||||
fromY = (150 + 260) / 2;
|
||||
happen.drag(fromX, fromY, 150, 300, function () {
|
||||
assert.equal(self.map.editable.activeEditor.feature._latlngs.length, 4);
|
||||
// New should have been inserted between first and second latlng,
|
||||
// so second should equal third, and first should not have changed
|
||||
assert.equal(first, self.map.editable.activeEditor.feature._latlngs[0]);
|
||||
assert.equal(second, self.map.editable.activeEditor.feature._latlngs[2]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#newHole', function () {
|
||||
|
||||
it('should create new hole on click', function () {
|
||||
assert.notOk(polygon._holes);
|
||||
polygon.editor.newHole();
|
||||
happen.at('mousemove', 150, 170);
|
||||
happen.at('click', 150, 170);
|
||||
assert.equal(polygon._holes.length, 1);
|
||||
assert.equal(polygon._holes[0].length, 1);
|
||||
happen.at('mousemove', 200, 250);
|
||||
happen.at('click', 200, 250);
|
||||
assert.equal(polygon._holes[0].length, 2);
|
||||
happen.at('mousemove', 250, 250);
|
||||
happen.at('click', 250, 250);
|
||||
assert.equal(polygon._holes[0].length, 3);
|
||||
happen.at('mousemove', 250, 200);
|
||||
happen.at('click', 250, 200);
|
||||
assert.equal(polygon._holes[0].length, 4);
|
||||
});
|
||||
|
||||
it('should not create new point when clicking outside', function () {
|
||||
happen.at('click', 400, 400);
|
||||
assert.equal(polygon._holes[0].length, 4);
|
||||
});
|
||||
|
||||
it('should finish shape on last point click', function () {
|
||||
happen.at('click', 250, 200);
|
||||
happen.at('click', 260, 210);
|
||||
assert.equal(polygon._holes[0].length, 4);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
129
test/PolylineEditor.js
Normal file
129
test/PolylineEditor.js
Normal file
@ -0,0 +1,129 @@
|
||||
describe('L.PolylineEditor', function() {
|
||||
var mouse, polyline;
|
||||
|
||||
before(function () {
|
||||
this.map = map;
|
||||
});
|
||||
after(function () {
|
||||
polyline.editor.disable();
|
||||
this.map.removeLayer(polyline);
|
||||
});
|
||||
|
||||
describe('#startNewLine()', function() {
|
||||
|
||||
it('should create feature and editor', function() {
|
||||
this.map.editable.startNewLine();
|
||||
assert.ok(this.map.editable.activeEditor);
|
||||
polyline = this.map.editable.activeEditor.feature;
|
||||
assert.ok(polyline);
|
||||
assert.notOk(this.map.editable.activeEditor.feature._latlngs.length);
|
||||
});
|
||||
|
||||
it('should create latlng on click', function () {
|
||||
happen.at('mousemove', 100, 150);
|
||||
happen.at('click', 100, 150);
|
||||
assert.equal(this.map.editable.activeEditor.feature._latlngs.length, 1);
|
||||
happen.at('mousemove', 200, 350);
|
||||
happen.at('click', 200, 350);
|
||||
assert.equal(this.map.editable.activeEditor.feature._latlngs.length, 2);
|
||||
happen.at('mousemove', 300, 250);
|
||||
happen.at('click', 300, 250);
|
||||
assert.equal(this.map.editable.activeEditor.feature._latlngs.length, 3);
|
||||
});
|
||||
|
||||
it('should finish shape on last point click', function () {
|
||||
happen.at('click', 300, 250);
|
||||
assert.equal(this.map.editable.activeEditor.feature._latlngs.length, 3);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#disable()', function () {
|
||||
|
||||
it('should stop editing on disable() call', function () {
|
||||
polyline.endEdit();
|
||||
assert.notOk(this.map.editable.activeEditor);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#enable()', function () {
|
||||
|
||||
it('should start editing on enable() call', function () {
|
||||
polyline.edit();
|
||||
assert.ok(this.map.editable.activeEditor);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#dragVertex()', function () {
|
||||
|
||||
it('should update latlng on vertex drag', function (done) {
|
||||
var before = this.map.editable.activeEditor.feature._latlngs[1].lat,
|
||||
self = this;
|
||||
happen.drag(200, 350, 220, 360, function () {
|
||||
assert.notEqual(before, self.map.editable.activeEditor.feature._latlngs[1].lat);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#deleteVertex()', function () {
|
||||
|
||||
it('should delete latlng on vertex click', function () {
|
||||
happen.at('click', 300, 250);
|
||||
assert.equal(this.map.editable.activeEditor.feature._latlngs.length, 2);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#continueForward()', function () {
|
||||
|
||||
it('should add new latlng on map click', function () {
|
||||
this.map.editable.activeEditor.continueForward();
|
||||
happen.at('mousemove', 400, 400);
|
||||
happen.at('click', 400, 400);
|
||||
assert.equal(this.map.editable.activeEditor.feature._latlngs.length, 3);
|
||||
happen.at('click', 400, 400); // Finish shape
|
||||
happen.at('click', 450, 450); // Click elsewhere on the map
|
||||
assert.equal(this.map.editable.activeEditor.feature._latlngs.length, 3);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#continueBackward()', function () {
|
||||
|
||||
it('should add new latlng on map click', function () {
|
||||
this.map.editable.activeEditor.continueBackward();
|
||||
happen.at('mousemove', 400, 100);
|
||||
happen.at('click', 400, 100);
|
||||
assert.equal(this.map.editable.activeEditor.feature._latlngs.length, 4);
|
||||
happen.at('click', 400, 100); // Finish shape
|
||||
happen.at('click', 450, 450); // Click elsewhere on the map
|
||||
assert.equal(this.map.editable.activeEditor.feature._latlngs.length, 4);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#dragMiddleMarker()', function (done) {
|
||||
|
||||
it('should insert new latlng on middle marker click', function (done) {
|
||||
var last = this.map.editable.activeEditor.feature._latlngs[3],
|
||||
third = this.map.editable.activeEditor.feature._latlngs[2],
|
||||
self = this,
|
||||
fromX = (400 + 220) / 2,
|
||||
fromY = (400 + 360) / 2;
|
||||
happen.drag(fromX, fromY, 300, 440, function () {
|
||||
assert.equal(self.map.editable.activeEditor.feature._latlngs.length, 5);
|
||||
// New should have been inserted between third and last latlng,
|
||||
// so third and last should not have changed
|
||||
assert.equal(last, self.map.editable.activeEditor.feature._latlngs[4]);
|
||||
assert.equal(third, self.map.editable.activeEditor.feature._latlngs[2]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
38
test/_pre.js
Normal file
38
test/_pre.js
Normal file
@ -0,0 +1,38 @@
|
||||
var qs = function (selector) {return document.querySelector(selector);};
|
||||
var qsa = function (selector) {return document.querySelectorAll(selector);};
|
||||
happen.at = function (what, x, y, props) {
|
||||
this.once(document.elementFromPoint(x, y), L.Util.extend({
|
||||
type: what,
|
||||
clientX: x,
|
||||
clientY: y,
|
||||
screenX: x,
|
||||
screenY: y,
|
||||
which: 1,
|
||||
button: 1
|
||||
}, props || {}));
|
||||
};
|
||||
happen.drag = function (fromX, fromY, toX, toY, then) {
|
||||
happen.at('mousemove', fromX, fromY);
|
||||
happen.at('mousedown', fromX, fromY);
|
||||
while (fromX <= toX) {
|
||||
happen.at('mousemove', fromX++, fromY);
|
||||
}
|
||||
var moveX = function () {
|
||||
if (fromX <= toX) {
|
||||
happen.at('mousemove', fromX++, fromY);
|
||||
window.setTimeout(moveX, 5);
|
||||
}
|
||||
};
|
||||
moveX();
|
||||
var moveY = function () {
|
||||
if (fromY <= toY) {
|
||||
happen.at('mousemove', fromX, fromY++);
|
||||
window.setTimeout(moveY, 5);
|
||||
}
|
||||
};
|
||||
moveY();
|
||||
window.setTimeout(function () {
|
||||
happen.at('mouseup', toX, toY);
|
||||
if (then) then();
|
||||
}, 1000);
|
||||
};
|
62
test/index.html
Normal file
62
test/index.html
Normal file
@ -0,0 +1,62 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Leaflet.Editable Tests</title>
|
||||
<meta charset="utf-8">
|
||||
<script src="../node_modules/leaflet/dist/leaflet-src.js"></script>
|
||||
<script src="../src/Leaflet.Editable.js"></script>
|
||||
<link rel="stylesheet" href="../node_modules/leaflet/dist/leaflet.css" />
|
||||
<script src="../node_modules/mocha/mocha.js"></script>
|
||||
<script src="../node_modules/happen/happen.js"></script>
|
||||
<script src="../node_modules/chai/chai.js"></script>
|
||||
<script src="../node_modules/effroi/dist/effroi.js"></script>
|
||||
<link rel="stylesheet" href="../node_modules/mocha/mocha.css" />
|
||||
<script>mocha.setup({ui: 'bdd', bail: true})</script>
|
||||
<script type="text/javascript">
|
||||
expect = chai.expect;
|
||||
assert = chai.assert;
|
||||
</script>
|
||||
<script src="./_pre.js"></script>
|
||||
<script src="./Editable.js"></script>
|
||||
<script src="./PolylineEditor.js"></script>
|
||||
<script src="./PolygonEditor.js"></script>
|
||||
<script src="./MarkerEditor.js"></script>
|
||||
<style type="text/css">
|
||||
#mocha {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 10000;
|
||||
background-color: white;
|
||||
box-shadow: 0px 0px 8px 0px black;
|
||||
overflow-y: auto;
|
||||
display: none;
|
||||
}
|
||||
#mocha-stats {
|
||||
position: absolute;
|
||||
}
|
||||
body { margin:0; padding:0; }
|
||||
#map { position:absolute; top:0; bottom:0; right: 0; left: 0; width:100%; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
<div id="map"></div>
|
||||
<script>
|
||||
var startPoint = [43.1249, 1.254],
|
||||
map = L.map('map').setView(startPoint, 16),
|
||||
tilelayer = L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {maxZoom: 19, attribution: 'Data \u00a9 <a href="http://www.openstreetmap.org/copyright"> OpenStreetMap Contributors </a>'}).addTo(map);
|
||||
var runner = mocha.run(function (failures) {
|
||||
if (window.location.hash !== '#debug') qs('#mocha').style.display = 'block';
|
||||
console.log(failures);
|
||||
});
|
||||
runner.on('fail', function(test, err) {
|
||||
console.log(test.title, test.err);
|
||||
console.log(test.err.expected, test.err.actual);
|
||||
console.log(test.err.stack);
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Reference in New Issue
Block a user