Removing geojson layers Mapbox.js/Leaflet.js - leaflet

I'm having trouble removing multiple geojson layers at a time. When I keep them as feature layers, there is memory of each and every layer added, one right after the other. But when they become marker layers only the last layer clicked is removed.
I've tried adding them to a group and calling clearLayers on the group, but that still only removes the last layer added, not all. Tried passing an id also, but that didn't seem to work either.
$(function() {
var geojson;
var map = L.mapbox.map('map')
.setView([29.9629, -90.0603], 6);
var filters = document.getElementById('filters');
var layerTwo = L.mapbox.featureLayer()
.loadURL('panel/data/tamorethan5.geojson')
.on('ready', layer)
var layerOne = L.mapbox.featureLayer()
.loadURL('panel/data/talessthan5.geojson')
.on('ready', layer)
var layerThree = L.mapbox.featureLayer()
.loadURL('panel/data/palessthan5.geojson')
.on('ready', layer)
var layerFour = L.mapbox.featureLayer()
.loadURL('panel/data/pamorethan5.geojson')
.on('ready', layer)
function layer() {
var assetLayerGroup = new L.LayerGroup();
var layer = this
var name = layer.getGeoJSON().name;
var item = filters.appendChild(document.createElement('div'));
var checkbox = item.appendChild(document.createElement('input'));
var label = item.appendChild(document.createElement('label'));
checkbox.type = 'checkbox';
checkbox.name ='radio';
checkbox.id = 'myCheckbox';
checkbox.value = name;
label.innerHTML = name;
label.class = label;
label.setAttribute('for', name);
checkbox.addEventListener('change', update);
function update(val) {
if (checkbox.checked) {
console.log(layer)
drawLocations(layer._geojson)
} else {
newMapLayer.clearLayers();
}
}
drawLocations = function(layer) {
L.stamp(layer)
newMapLayer = L.geoJson(layer, {
style: style,
onEachFeature: onEachFeature,
pointToLayer: function(feature, latlng) {
var rad = 3;
var col = getColor(feature.properties, 'status');
return L.circleMarker(latlng, {
radius: rad,
fillColor: "#ff7800",
stroke: true,
weight: 2,
opacity: 0.8,
fillOpacity: 0.8,
className: "circle"
});
}
}).addTo(map);
};
} //end layer

Hm, okay - a few tweaks are necessary or recommended here:
drawLocations(layer._geojson)
Anything starting with a underscore in leaflet or Mapbox should be considered off-limits. Use the .getGeoJSON method instead.
drawLocations(layer.getGeoJSON());
It doesn't look like you're actually adding layers to the assetLayerGroup. If you wanted to do that, you would call
var assetLayerGroup = L.layerGroup().addTo(map);
Right after the
var map = L.mapbox.map('map')
.setView([29.9629, -90.0603], 6);
lines, and instead of calling .addTo(map) on your L.geoJson layers, you would call .addTo(assetLayerGroup).
Then calling assetLayerGroup.clearLayers() would remove all of the added layers.

Related

Leaflet Layer control same map as base and overlay

I have 3 maps Osm, Lidar and Aerial . I want to select one as the baselayer and one as an overlay. E.g. osm + aerial, osm + Lidar, Aerial + lidar. The LayerControl nearly does what I want but fires baselayerchanged when an overlay is enabled that is also a baselayer optionn. Any ideas on how to sort this would be welcome.
Use two instances of L.Control.Layers, each having a different set of 3 "baselayers".
The nomenclature of "baselayers" and "overlays" can be confusing. As far as a L.Control.Layers is concerned, baselayers are mutually exclusive and overlays are not. You can have something that looks like a graphical overlay but is defined as a baselayer in a control.
Here is the code I ended up with. The important thing is that when changing base layer with an overlay selected, the overlays have to be unloaded to load the new base at the bottom of the z order.
$(document).ready(function () {
var map = L.map('map').setView([-14.0349289249, -171.438639761], 14);
var osm = L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 18,
attribution: 'Map data © OpenStreetMap contributors'
});
var aerial =L.tileLayer('aerial/{z}/{x}/{y}.png', {
maxZoom: 18,
tms: false,
attribution: 'Aerial from Centre for Samoan Studies (CSS)'
});
var lidar =L.tileLayer('lidar/{z}/{x}/{y}.png', {
maxZoom: 18,
tms: false,
attribution: 'LIDAR from Centre for Samoan Studies (CSS)'
});
// our set of layers
var layers = [osm, aerial, lidar];
// initial base layer
osm.addTo(map)
// to enable base/overlay swapping, add extra properties with unlikely to clash names
osm.isSamoaBase = true;
osm.isSamoaOverlay = false;
osm.samoaName = 'Base';
lidar.isSamoaBase = false;
lidar.isSamoaOverlay = false;
lidar.samoaName = 'LIDAR';
aerial.isSamoaBase = false;
aerial.samoaName = 'Aerial';
aerial.isSamoaOverlay = false;
// set true when the existing base layer is to be replaced
var baseChange = false;
// layer selector for both base and overlays,
// sorted to preserve order as layers move between base and overlay sections
var layerControl = L.control.layers(null, null, {sortLayers:true});
layerControl.addTo(map);
loadLayerControl(layerControl);
map.on('layerremove', function(le){
if (le.layer.isSamoaBase) {
le.layer.isSamoaBase = false;
// a new base will be selected next
baseChange = true;
}
if (le.layer.isSamoaOverlay) {
le.layer.isSamoaOverlay = false;
}
loadLayerControl(layerControl);
});
map.on('layeradd', function(le){
if (baseChange) {
baseChange = false;
// tag as new base
le.layer.isSamoaBase = true;
le.layer.isSamoaOverlay = false;
// restore full base layer opacity
le.layer.setOpacity(1);
}
if (!le.layer.isSamoaBase) {
le.layer.isSamoaOverlay = true;
}
loadLayerControl(layerControl);
//set opacity from slider for any selected overlays
var val = $('#basemapslider').slider("option", "value");
map.eachLayer(function(layer){
if (layer.isSamoaOverlay) {
layer.setOpacity(val);
}
});
});
function loadLayerControl(lc) {
//remove twice in case both layer both a base and overlay option
for (var i = 0; i < layers.length; i++) {
lc.removeLayer(layers[i]);
lc.removeLayer(layers[i]);
}
// must add base first to get base at bottom of z order
for (var i = 0; i < layers.length; i++) {
if (!layers[i].isSamoaOverlay) {
lc.addBaseLayer(layers[i], layers[i].samoaName);
}
}
// finally add overlays
for (var i = 0; i < layers.length; i++) {
if (!layers[i].isSamoaBase) {
lc.addOverlay(layers[i], layers[i].samoaName);
}
}
}
})

Leaflet error when zoom after close popup

I created a map and added markers on it in salesforce lightning component ,
issue is:
click on marker >> click on map (this close the popup) >> zoom in and out >> this error displayed for each zoom step:
Uncaught TypeError: Cannot read property '_latLngToNewLayerPoint' of
null throws at /resource/1498411629000/leaflet/leaflet.js:9:10763
this is the code in component javascript helper:
({
drawMap: function(component,map){
var mapElement = component.find("map").getElement();
map = L.map(mapElement).setView([35.232, 40.5656], 12);
L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}',
{
attribution: 'Tiles © Esri'
}).addTo(map);
var markers = L.marker([35.232, 40.5656]).addTo(map);
component.set("v.map", map);
component.set("v.markers", markers);
},
putMarkers: function(component) {
var map = component.get('v.map');
var markers = component.get('v.markers');
var projects = component.get('v.projects');
var projectsAction = component.get("c.getProjectsList");
var markerArray = [];
var symbol;
var symbol1 = 'symbolURl';
var symbol2 = 'symbolURl';
projectsAction.setCallback(this, function(response) {
var state = response.getState();
if( state == "SUCCESS"){
projects = response.getReturnValue();
if (markers) {
markers.remove();
}
// Add Markers
if (map && projects && projects.length> 0) {
for (var i=0; i<projects.length; i++) {
var project = projects[i];
if (project.Latitude && project.Longitude) {
var latLng = [project.Latitude, project.Longitude];
var currentStatus = project.status;
if(currentStatus=="status1")
symbol = symbol1;
else if(currentStatus=="status2")
symbol = symbol2;
var popupTemplate = '<b style="color:black; font-size:11px;">Project Name:</b><span style="float:right;margin-right:10px;color:#800000;font-size:11px;width:110px;text-align:right; white-space:normal;" > '+project.name;
var icon = new L.DivIcon({
className:'',
html: '<img style="width:34px;height:37px;" src="'+symbol+'"/>'+
'<span style="font-weight:bold;font-size:9pt;">text</span>'
});
var marker = L.marker(latLng, {project: project,icon:icon,title:project.name}).bindPopup(popupTemplate,{minWidth:200});
markerArray.push(marker);
}
}
L.layerGroup(markerArray).addTo(map);
component.set("v.map", map);
}}
else if(state == "ERROR"){
console.log('Error in calling server side action');
}
});
$A.enqueueAction(projectsAction);
}
and in javascript controller:
jsLoaded: function(component, event, helper) {
var map = component.get("v.map");
if (!map) {
helper.drawMap(component,map);
helper.putMarkers(component);
}
},
projectsChangeHandler: function(component, event, helper) {
helper.putMarkers(component);
}
where is the problem? please help
this is a new bug for Vue3,I've solved it:
change Proxy'map to toRaw(map),like this:
L.geoJSON(geoJson, {
style: {
color: '#ff0000',
fillColor: "#ff000000",
weight: 1
},
onEachFeature: myonEachFeature,
}).addTo(toRaw(map))
I solved it by editing the leaflet.js file:
search for the word ( _latLngToNewLayerPoint )
and wrap the code with the condition:
if(this._map)
so if _map is null, skip
I don't know why this bug is only presented in salesforce
In my case it was related to the framework variable wrappers (vue.js).
When I use observable variable like this:
data() {
return {
map: null,
};
},
mounted() {
// here it's required to use a global variable
// or a variable without framework handlers
// map = L.map(...);
this.map = L.map('map', { preferCanvas: true }).setView([51.505, -0.09], 13);
}
I get error:
Popup.js:231 Uncaught TypeError: Cannot read property '_latLngToNewLayerPoint' of null
at NewClass._animateZoom (Popup.js:231)
at NewClass.fire (Events.js:190)
at NewClass._animateZoom (Map.js:1689)
at NewClass.<anonymous> (Map.js:1667)
The solution is just to define the map variable outside of framework, in other words use a global variable for leaflet structures.

How to add an img to L.control.layers?

Is there a way to add an icon/img before the input checkbox inside the layer control?
And is there a way to add a value(or id) prop to the checkbox?
For now I can add an icon with this, but that is not exacltly what I want. Thanks.
L.control.layers({
null
}, {
'<img src="/img/fish.png">Some text':new L.layerGroup(),
}).addTo(map);
This will add an img after the checkbox. Maybe somehow override the _addItem method in the Control.Layers.js, but I don't know how.
Update: Is there a way to add a value prop to the checkbox on this stage?
var layers = L.control.layers({}, {
'name':new L.layerGroup(), // how to add val?
}).addTo(map);
So I can add a value and name(span, label) to the checkbox to get the
<div>
<input type="checkbox" value="some val" class="leaflet-control-layers-selector"><span>name</span>
</div>
Might want to do this with custom JavaScript. I don't believe there is any built-in way to accomplish this. Try something like this:
Save the control layers to a variable:
var layers = L.control.layers({}, {'name' : new L.layerGroup()}).addTo(map);
Get the _overlaysList property (unless you're altering a base map):
var list = layers._overlaysList;
Iterate the input tags:
var inputs = list.getElementsByTagName('input');
Find the one you want to alter, and prepend an image to it.
Well, here is my solution, if someone is interested
//---------------- OVERRIDING THE LAYERS -------------------
L.Control.IconLayers = L.Control.Layers.extend({
initialize: function (baseLayers, overlays, options) {
L.Control.Layers.prototype.initialize.call(this, baseLayers, overlays, options);
},
_addItem: function (obj) {
//console.log("Layer Control:",obj)
var label = document.createElement('label'),
input, icon = false,
checked = this._map.hasLayer(obj.layer);
if (obj.overlay) {
input = document.createElement('input');
input.type = 'checkbox';
input.className = 'leaflet-control-layers-selector';
input.defaultChecked = checked;
input.value = obj.name; // add
console.log(obj)
if ('getIcon' in obj.layer) {
icon = obj.layer.getIcon();
}
} else {
input = this._createRadioElement('leaflet-base-layers', checked);
}
var layer_name = obj.name
if (obj.layer.hasOwnProperty('_options')){
layer_name = obj.layer._options.name
input.id = obj.layer._options.id
}
input.layerId = L.stamp(obj.layer);
L.DomEvent.on(input, 'click', this._onInputClick, this);
var name = document.createElement('span');
name.innerHTML = ' ' + layer_name;
label.appendChild(input);
if (icon) {
var i = document.createElement('span');
i.innerHTML = icon;
label.appendChild(i);
}
label.appendChild(name);
var container = obj.overlay ? this._overlaysList : this._baseLayersList;
container.appendChild(label);
return label;
}
});
L.control.iconLayers = function(baseLayers, overlays, options) {
return new L.Control.IconLayers(baseLayers, overlays, options);
}
L.customLayerGroup = L.LayerGroup.extend({
initialize: function (layers) {
console.log("LAYERS:",layers)
L.LayerGroup.prototype.initialize.call(this, layers);
this._options = layers;
},
});
//---------------- OVERRIDING THE LAYERS -------------------
var layers = L.control.iconLayers({
'Mapbox Streets': L.mapbox.tileLayer('mapbox.streets').addTo(map),
'Mapbox Light': L.mapbox.tileLayer('mapbox.light')
}, {
'1':new L.layerGroup(),
'2':new L.layerGroup(),
'3':new L.customLayerGroup({name:"Boats",id:"3", value:"3"}),
}).addTo(map);

Mapbox and Leaflet Draw edit only 1 group

I want to add multiple layergroup or featuregroup objects or something else if there's a better way on a map that can contain multiple polygons in each. I want these groups to be distinguishable so they can be used in conjunction with datatables and the leaflet draw tool so that when I select a row within datatables, only the polygons within the related group can be edited or deleted using the draw tool and any new polygons are only added to that group.
I've done this before with only a single polygon and one featuregroup, but how do I handle multiple groups?
featureGroup = L.featureGroup().addTo(map);
var drawControl = new L.Control.Draw({
edit: {
featureGroup: featureGroup
},
draw: {
marker: false,
polygon: true,
polyline: false,
rectangle: false,
circle: false
},
locate: {
marker: true
}
}).addTo(map);
$('.leaflet-control-zoom').css('margin-top', '45px');
L.drawLocal.draw.toolbar.buttons.polygon = 'Draw damage area';
L.drawLocal.edit.toolbar.buttons.edit = 'Edit damage area';
L.drawLocal.edit.toolbar.buttons.remove = 'Delete damage area';
if (jsModel.polygonpoints)
{
var polygonPoints = jsModel.polygonpoints.split(';');
if (polygonPoints.length > 0) {
var polyPoints = [];
for (var i = 0; i < polygonPoints.length; i++) {
var point = polygonPoints[i].split(',');
polyPoints.push([parseFloat(point[0]), parseFloat(point[1])]);
}
var points = polyPoints;
var polyOptions = {
color: '#f06eaa'
};
var polygon;
if (typeof featureGroup !== "undefined") {
polygon = L.polygon(points, polyOptions).addTo(self.featureGroup);
self.polygon = polygon;
}
}
}
I'm trying to add the polygons during a render of my datatable columns. Here is what I have so far. I saw a post somewhere that talked about adding multiple draw tools and then only make the current one available. Not sure if this is a good way to do it, but if it is, what can I use in my button's "data-draw-control" to be able to point to the one I want to be current?
"columns": [
{
"render": function (data, type, row) {
var featureGroup = L.featureGroup().addTo(map);
var drawControl = new L.Control.Draw({
edit: {
featureGroup: featureGroup
},
draw: {
marker: false,
polygon: true,
polyline: false,
rectangle: false,
circle: false
}
}).addTo(map);
for (var al = 0; al < row.areaList.length; al++)
{
var polyPoints = [];
for (var ap = 0; ap < row.areaList[al].areaPointList.length; ap++)
{
if (row.areaList[al].areaPointList[ap].x == 0 && row.areaList[al].areaPointList[ap].y == 0)
{
if (polyPoints.length != 0)
{
//add polygon to map
L.polygon(polyPoints).addTo(featureGroup);
polyPoints.length = 0;
}
}
else
{
polyPoints.push([parseFloat(row.areaList[al].areaPointList[ap].x), parseFloat(row.areaList[al].areaPointList[ap].y)]);
}
}
}
return "<button type='button' class='btn-zoom' data-draw-control='" + drawControl + "'><i class='fa fa-search'></i></button";
}
}
First create two featuregroups and a layercontrol so you can toggle between them:
var layerControl = new L.Control.Layers({
'FeatureGroup 1': new L.FeatureGroup(),
'FeatureGroup 2': new L.FeatureGroup()
}).addTo(map);
Now then you toggle between those, the map fires a basemapchanged event which contains the currently selected featuregroup, store that in a variable:
var currentFeatureGroup;
map.on('baselayerchange', function (e) {
currentFeatureGroup = e.layer;
});
When you draw something, the map fires another event where you can process the drawn feature. Create a function that returns the currently selected featuregroup and store it into that:
function getCurrentFeatureGroup () {
return currentFeatureGroup;
}
map.on('draw:created', function (e) {
var featureGroup = getCurrentFeatureGroup();
if (featureGroup instanceof L.FeatureGroup) {
featureGroup.addLayer(e.layer);
} else {
alert('Please select a featuregroup');
}
});
Here's an example of the concept on Plunker: http://plnkr.co/edit/9ZEjuP?p=preview

find associated polylines for selected marker/circlemarker/point in leaflet

I have a multiple circlemarkers on map.
I want to find how many polylines passes through that marker & want to remove that polyline & If polyline does not exists then I want to add polyline.
I am using leaflet.
<script type="text/javascript">
function init(){
var map = L.map('map').setView([51.49521, -0.10062], 13);
L.tileLayer('http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png', {
maxZoom: 18,
attribution: 'Map data © OpenStreetMap contributors, CC-BY-SA, Imagery © CloudMade'
}).addTo(map);
// get all 6 points
var points = [
[51.49346, -0.11518],
[51.49827, -0.06763],
[51.48331, -0.08154],
[51.52284, -0.09974],
[51.51932, -0.06695],
[51.50949, -0.1363]
];
// polyline
var selection = [];
var polyline = new L.Polyline([], {
color: 'blue',
weight: 5,
smoothFactor: 1
}).addTo(map);
var changeMarkerState = function (marker, select) {
if (marker instanceof L.CircleMarker) {
if (select) {
marker.setRadius(25);
} else {
marker.setRadius(10);
}
}
if (marker instanceof L.Marker) {
if (select) {
marker.options.title = 'selected';
} else {
marker.options.title = 'unselected';
}
marker.setIcon(new L.Icon.Default());
}
};
var onClick = function () {
var index = selection.indexOf(this);
if (index !== -1) {
changeMarkerState(this, false);
selection.splice(index, 1);
polyline.spliceLatLngs(index, 1);
} else {
changeMarkerState(this, true);
selection.push(this);
polyline.addLatLng(this.getLatLng())
}
};
// centerpoint
var centerPoint = new L.LatLng(51.49521, -0.10062);
var marker1 = L.marker([51.49521, -0.10062],
{title: 'unselected'}).on('click', onClick).addTo(map);
// adding allo points to map
for (var i = 0, l = points.length; i < l; i++)
{
// here I can use marker also(if solution is possible with markers)
L.circleMarker(points[i]).on('click', onClick).addTo(map);
var myPoint = new L.LatLng(points[i][0],points[i][1]);
var myPointList = [myPoint, centerPoint];
var firstpolyline = new L.Polyline(myPointList, {
color: 'red',
weight: 5,
smoothFactor: 1
}).addTo(map);
}
}
</script>
In above code what I am doing is that I am drawing multiple red polylines from different circlemarkers to one center point.
On selection of two circle markers I am drawing blue polyline between them.
At same time I want to remove the red polyline which there between circlemarkers & centerpoint.
Also If circlemarker is unselected then that red polyline between that circlemarker & centerpoint should be added.
To compare two latlngs use L.LatLng.equals: https://github.com/Leaflet/Leaflet/blob/master/src/geo/LatLng.js#L24.
To get latlngs from polyline use L.Polyline.getLatLngs: https://github.com/Leaflet/Leaflet/blob/master/src/layer/vector/Polyline.js#L34.
Now you can get your point in polyline and remove it using L.Polyline.spliceLatLngs: https://github.com/Leaflet/Leaflet/blob/master/src/layer/vector/Polyline.js#L48 or add using L.Polyline.addLatLng: https://github.com/Leaflet/Leaflet/blob/master/src/layer/vector/Polyline.js#L43 or set using L.Polyline.setLatLngs: https://github.com/Leaflet/Leaflet/blob/master/src/layer/vector/Polyline.js#L38.