L.Polyline.polylineEditor = L.Polyline.extend({ _prepareMapIfNeeded: function() { var that = this; that._changed = false; if(this._map._editablePolylines != null) { return } // Container for all editable polylines on this map: this._map._editablePolylines = []; this._map._editablePolylinesEnabled = true; // Click anywhere on map to add a new point-polyline: if(this._options.newPolylines) { console.log('click na map'); that._map.on('dblclick', function(event) { console.log('click, target=' + (event.target == that._map) + ' type=' + event.type); if(that._map.isEditablePolylinesBusy()) return; var latLng = event.latlng; if(that._options.newPolylineConfirmMessage) if(!confirm(that._options.newPolylineConfirmMessage)) return var contexts = [{'originalPolylineNo': null, 'originalPointNo': null}]; L.Polyline.PolylineEditor([latLng], that._options, contexts).addTo(that._map); that._showBoundMarkers(); that._changed = true; }); } /** * Check if there is *any* busy editable polyline on this map. */ this._map.isEditablePolylinesBusy = function() { var map = this; for(var i = 0; i < map._editablePolylines.length; i++) if(map._editablePolylines[i]._isBusy()) return true; return false; }; /** * Enable/disable editing. */ this._map.setEditablePolylinesEnabled = function(enabled) { var map = this; map._editablePolylinesEnabled = enabled; for(var i = 0; i < map._editablePolylines.length; i++) { var polyline = map._editablePolylines[i]; if(enabled) { polyline._showBoundMarkers(); } else { polyline._hideAll(); } } }; /* * Utility method added to this map to retreive editable * polylines. */ this._map.getEditablePolylines = function() { var map = this; return map._editablePolylines; } this._map.fixAroundEditablePoint = function(marker) { var map = this; for(var i = 0; i < map._editablePolylines.length; i++) { var polyline = map._editablePolylines[i]; polyline._reloadPolyline(marker); } } }, /** * Will add all needed methods to this polyline. */ _addMethods: function() { var that = this; this._init = function(options, contexts) { this._prepareMapIfNeeded(); /** * Since all point editing is done by marker events, markers * will be the main holder of the polyline points locations. * Every marker contains a reference to the newPointMarker * *before* him (=> the first marker has newPointMarker=null). */ this._parseOptions(options); this._markers = []; var points = this.getLatLngs(); var length = points.length; for(var i = 0; i < length; i++) { var marker = this._addMarkers(i, points[i]); if(! ('context' in marker)) { marker.context = {} if(that._contexts != null) { marker.context = contexts[i]; } } if(marker.context && ! ('originalPointNo' in marker.context)) marker.context.originalPointNo = i; if(marker.context && ! ('originalPolylineNo' in marker.context)) marker.context.originalPolylineNo = that._map._editablePolylines.length; } // Map move => show different editable markers: var map = this._map; this._map.on("zoomend", function(e) { that._showBoundMarkers(); }); this._map.on("moveend", function(e) { that._showBoundMarkers(); }); if(this._desiredPolylineNo && this._desiredPolylineNo != null) { this._map._editablePolylines.splice(this._desiredPolylineNo, 0, this); } else { this._map._editablePolylines.push(this); } }; /** * Check if is busy adding/moving new nodes. Note, there may be * *other* editable polylines on the same map which *are* busy. */ this._isBusy = function() { return that._busy; }; this._setBusy = function(busy) { that._busy = busy; }; /** * Get markers for this polyline. */ this.getPoints = function() { return this._markers; }; this.isChanged = function() { return this._changed; } this._parseOptions = function(options) { if(!options) options = {}; // Do not show edit markers if more than maxMarkers would be shown: if(!('maxMarkers' in options)) options.maxMarkers = 100; if(!('newPolylines' in options)) options.newPolylines = false; if(!('newPolylineConfirmMessage' in options)) options.newPolylineConfirmMessage = ''; if(!('addFirstLastPointEvent' in options)) options.addFirstLastPointEvent = 'click'; if(!('customPointListeners' in options)) options.customPointListeners = {}; if(!('customNewPointListeners' in options)) options.customNewPointListeners = {}; this._options = options; // Icons: if(!options.pointIcon) this._options.pointIcon = L.icon({ iconUrl: 'editmarker.png', iconSize: [11, 11], iconAnchor: [6, 6] }); if(!options.newPointIcon) this._options.newPointIcon = L.icon({ iconUrl: 'editmarker2.png', iconSize: [11, 11], iconAnchor: [6, 6] }); }; /** * Show only markers in current map bounds *is* there are only a certain * number of markers. This method is called on eventy that change map * bounds. */ this._showBoundMarkers = function() { if (!that._map) { return; } this._setBusy(false); if(!that._map._editablePolylinesEnabled) { console.log('Do not show because editing is disabled'); return; } var bounds = that._map.getBounds(); var found = 0; for(var polylineNo in that._map._editablePolylines) { var polyline = that._map._editablePolylines[polylineNo]; for(var markerNo in polyline._markers) { var marker = polyline._markers[markerNo]; if(bounds.contains(marker.getLatLng())) found += 1; } } for(var polylineNo in that._map._editablePolylines) { var polyline = that._map._editablePolylines[polylineNo]; for(var markerNo in polyline._markers) { var marker = polyline._markers[markerNo]; if(found < that._options.maxMarkers) { that._setMarkerVisible(marker, bounds.contains(marker.getLatLng())); that._setMarkerVisible(marker.newPointMarker, markerNo > 0 && bounds.contains(marker.getLatLng())); } else { that._setMarkerVisible(marker, false); that._setMarkerVisible(marker.newPointMarker, false); } } } }; /** * Used when adding/moving points in order to disable the user to mess * with other markers (+ easier to decide where to put the point * without too many markers). */ this._hideAll = function(except) { this._setBusy(true); for(var polylineNo in that._map._editablePolylines) { console.log("hide " + polylineNo + " markers"); var polyline = that._map._editablePolylines[polylineNo]; for(var markerNo in polyline._markers) { var marker = polyline._markers[markerNo]; if(except == null || except != marker) polyline._setMarkerVisible(marker, false); if(except == null || except != marker.newPointMarker) polyline._setMarkerVisible(marker.newPointMarker, false); } } } /** * Show/hide marker. */ this._setMarkerVisible = function(marker, show) { if(!marker) return; var map = this._map; if(show) { if(!marker._visible) { if(!marker._map) { // First show for this marker: marker.addTo(map); } else { // Marker was already shown and hidden: map.addLayer(marker); } marker._map = map; } marker._visible = true; } else { if(marker._visible) { map.removeLayer(marker); } marker._visible = false; } }; /** * Reload polyline. If it is busy, then the bound markers will not be * shown. */ this._reloadPolyline = function(fixAroundPointNo) { that.setLatLngs(that._getMarkerLatLngs()); if(fixAroundPointNo != null) that._fixAround(fixAroundPointNo); that._showBoundMarkers(); that._changed = true; } /** * Add two markers (a point marker and his newPointMarker) for a * single point. * * Markers are not added on the map here, the marker.addTo(map) is called * only later when needed first time because of performance issues. */ this._addMarkers = function(pointNo, latLng, fixNeighbourPositions) { var that = this; var points = this.getLatLngs(); var marker = L.marker(latLng, {draggable: true, icon: this._options.pointIcon}); marker.newPointMarker = null; marker.on('dragstart', function(event) { var pointNo = that._getPointNo(event.target); var previousPoint = pointNo && pointNo > 0 ? that._markers[pointNo - 1].getLatLng() : null; var nextPoint = pointNo < that._markers.length - 1 ? that._markers[pointNo + 1].getLatLng() : null; that._setupDragLines(marker, previousPoint, nextPoint); that._hideAll(marker); }); marker.on('dragend', function(event) { var marker = event.target; var pointNo = that._getPointNo(event.target); setTimeout(function() { that._reloadPolyline(pointNo); }, 25); }); marker.on('contextmenu', function(event) { var marker = event.target; var pointNo = that._getPointNo(event.target); that._map.removeLayer(marker); that._map.removeLayer(newPointMarker); that._markers.splice(pointNo, 1); that._reloadPolyline(pointNo); }); marker.on(that._options.addFirstLastPointEvent, function(event) { console.log('click on marker'); var marker = event.target; var pointNo = that._getPointNo(event.target); console.log('pointNo=' + pointNo + ' that._markers.length=' + that._markers.length); event.dont; if(pointNo == 0 || pointNo == that._markers.length - 1) { console.log('first or last'); that._prepareForNewPoint(marker, pointNo == 0 ? 0 : pointNo + 1); } else { console.log('not first or last'); } }); var previousPoint = points[pointNo == 0 ? pointNo : pointNo - 1]; var newPointMarker = L.marker([(latLng.lat + previousPoint.lat) / 2., (latLng.lng + previousPoint.lng) / 2.], {draggable: true, icon: this._options.newPointIcon}); marker.newPointMarker = newPointMarker; newPointMarker.on('dragstart', function(event) { var pointNo = that._getPointNo(event.target); var previousPoint = that._markers[pointNo - 1].getLatLng(); var nextPoint = that._markers[pointNo].getLatLng(); that._setupDragLines(marker.newPointMarker, previousPoint, nextPoint); that._hideAll(marker.newPointMarker); }); newPointMarker.on('dragend', function(event) { var marker = event.target; var pointNo = that._getPointNo(event.target); that._addMarkers(pointNo, marker.getLatLng(), true); setTimeout(function() { that._reloadPolyline(); }, 25); }); newPointMarker.on('contextmenu', function(event) { // 1. Remove this polyline from map var marker = event.target; var pointNo = that._getPointNo(marker); var markers = that.getPoints(); that._hideAll(); var secondPartMarkers = that._markers.slice(pointNo, pointNo.length); that._markers.splice(pointNo, that._markers.length - pointNo); that._reloadPolyline(); var points = []; var contexts = []; for(var i = 0; i < secondPartMarkers.length; i++) { var marker = secondPartMarkers[i]; points.push(marker.getLatLng()); contexts.push(marker.context); } console.log('points:' + points); console.log('contexts:' + contexts); // Need to know the current polyline order numbers, because // the splitted one need to be inserted immediately after: var originalPolylineNo = that._map._editablePolylines.indexOf(that); L.Polyline.PolylineEditor(points, that._options, contexts, originalPolylineNo + 1) .addTo(that._map); that._showBoundMarkers(); }); this._markers.splice(pointNo, 0, marker); // User-defined custom event listeners: if(that._options.customPointListeners) for(var eventName in that._options.customPointListeners) marker.on(eventName, that._options.customPointListeners[eventName]); if(that._options.customNewPointListeners) for(var eventName in that._options.customNewPointListeners) newPointMarker.on(eventName, that._options.customNewPointListeners[eventName]); if(fixNeighbourPositions) { this._fixAround(pointNo); } return marker; }; /** * Event handlers for first and last point. */ this._prepareForNewPoint = function(marker, pointNo) { // This is slightly delayed to prevent the same propagated event // to be catched here: setTimeout( function() { that._hideAll(); that._setupDragLines(marker, marker.getLatLng()); that._map.once('click', function(event) { if(that._markers.length == 1) { pointNo += 1; } console.log('dodajemo na ' + pointNo + ' - ' + event.latlng); that._addMarkers(pointNo, event.latlng, true); that._reloadPolyline(); }); }, 100 ); }; /** * Fix nearby new point markers when the new point is created. */ this._fixAround = function(pointNoOrMarker) { if((typeof pointNoOrMarker) == 'number') var pointNo = pointNoOrMarker; else var pointNo = that._markers.indexOf(pointNoOrMarker); if(pointNo < 0) return; var previousMarker = pointNo == 0 ? null : that._markers[pointNo - 1]; var marker = that._markers[pointNo]; var nextMarker = pointNo < that._markers.length - 1 ? that._markers[pointNo + 1] : null; if(marker && previousMarker) { marker.newPointMarker.setLatLng([(previousMarker.getLatLng().lat + marker.getLatLng().lat) / 2., (previousMarker.getLatLng().lng + marker.getLatLng().lng) / 2.]); } if(marker && nextMarker) { nextMarker.newPointMarker.setLatLng([(marker.getLatLng().lat + nextMarker.getLatLng().lat) / 2., (marker.getLatLng().lng + nextMarker.getLatLng().lng) / 2.]); } }; /** * Find the order number of the marker. */ this._getPointNo = function(marker) { for(var i = 0; i < this._markers.length; i++) { if(marker == this._markers[i] || marker == this._markers[i].newPointMarker) { return i; } } return -1; }; /** * Get polyline latLngs based on marker positions. */ this._getMarkerLatLngs = function() { var result = []; for(var i = 0; i < this._markers.length; i++) result.push(this._markers[i].getLatLng()); return result; }; this._setupDragLines = function(marker, point1, point2) { var line1 = null; var line2 = null; if(point1) line1 = L.polyline([marker.getLatLng(), point1], {dasharray: "5,1", weight: 1}) .addTo(that._map); if(point2) line2 = L.polyline([marker.getLatLng(), point2], {dasharray: "5,1", weight: 1}) .addTo(that._map); var moveHandler = function(event) { if(line1) line1.setLatLngs([event.latlng, point1]); if(line2) line2.setLatLngs([event.latlng, point2]); }; var stopHandler = function(event) { if (that._map) { that._map.off('mousemove', moveHandler); marker.off('dragend', stopHandler); if(line1) that._map.removeLayer(line1); if(line2) that._map.removeLayer(line2); console.log('STOPPED'); if(event.target != that._map) { that._map.fire('click', event); } } }; that._map.on('mousemove', moveHandler); marker.on('dragend', stopHandler); that._map.once('click', stopHandler); marker.once('click', stopHandler); if(line1) line1.once('click', stopHandler); if(line2) line2.once('click', stopHandler); } } }); L.Polyline.polylineEditor.addInitHook(function () { this.on('add', function(event) { this._map = event.target._map; this._addMethods(); /** * When addint a new point we must disable the user to mess with other * markers. One way is to check everywhere if the user is busy. The * other is to just remove other markers when the user is doing * somethinng. * * TODO: Decide the right way to do this and then leave only _busy or * _hideAll(). */ this._busy = false; this._initialized = false; this._init(this._options, this._contexts); this._initialized = true; return this; }); this.on('remove', function(event) { var polyline = event.target; var map = polyline._map; var polylines = map.getEditablePolylines(); var index = polylines.indexOf(polyline); if (index > -1) { polylines[index]._markers.forEach(function(marker) { map.removeLayer(marker); if(marker.newPointMarker) map.removeLayer(marker.newPointMarker); }); polylines.splice(index, 1); } }); }); /** * Construct a new editable polyline. * * latlngs ... a list of points (or two-element tuples with coordinates) * options ... polyline options * contexts ... custom contexts for every point in the polyline. Must have the * same number of elements as latlngs and this data will be * preserved when new points are added or polylines splitted. * polylineNo ... insert this polyline in a specific order (used when splitting). * * More about contexts: * This is an array of objects that will be kept as "context" for every * point. Marker will keep this value as marker.context. New markers will * have context set to null. * * Contexts must be the same size as the polyline size! * * By default, even without calling this method -- every marker will have * context with one value: marker.context.originalPointNo with the * original order number of this point. The order may change if some * markers before this one are delted or new added. */ L.Polyline.PolylineEditor = function(latlngs, options, contexts, polylineNo) { // Since the app code may not be able to explicitly call the // initialization of all editable polylines (if the user created a new // one by splitting an existing), with this method you can control the // options for new polylines: if(options.prepareOptions) { options.prepareOptions(options); } var result = new L.Polyline.polylineEditor(latlngs, options); result._options = options; result._contexts = contexts; result._desiredPolylineNo = polylineNo return result; };