I load some lat / lon info, then use it to build a polyline.
I then want to add a marker at each of the polyline vertices that will show when the polyline is clicked.
The vertices should disappear if a different (or the same) polyline is clicked.
The code below creates the polyline and the vertex markers.
But the vertex markers do not ever disappear.
I've tried to do this several ways with the same result. I've tried storing the vertex markers in an array and adding them directly to the map, then map.removeLayer'ing them. That doesn't work either. Nor does it work if I use an L.featureGroup instead of a layerGroup.
Clearly I've missed the point somewhere as to how markers can be removed. Could someone point me at the error in my methodology?
// trackMarkersVisible is a global L.layerGroup
// success is a callback from an ajax that fetches trackData, an array f lat/lon pairs
success: function (trackData) {
// build an array of latLng's
points = buildTrackPointSet(trackData, marker.deviceID);
var newTrack = L.polyline(
points, {
color: colors[colorIndex],
weight: 6,
clickable: true
}
);
$(newTrack).on("click", function () {
trackMarkersVisible.clearLayers();
$.each(points, function(idx, val) {
var tm = new L.Marker(val);
trackMarkersVisible.addLayer(tm);
});
map.addLayer(trackMarkersVisible);
});
}
Without a JSFiddle or Plunker it's hard to say because i'm not sure what behaviour your getting but using the clearLayers() method of L.LayerGroup should remove all layers from that group. I would check in the onclick handler if the group already has layers: group.getLayers().length === 0 If the group is empty, add the markers, if the group has layers use clearLayers. Example in code:
polyline.on('click', function (e) {
if (group.getLayers().length === 0) {
e.target._latlngs.forEach(function (latlng) {
group.addLayer(L.marker(latlng));
});
} else {
group.clearLayers();
}
});
This works for me, see the example on Plunker: http://plnkr.co/edit/7IPHrO?p=preview
FYI: an instance of L.Polyline is always clickable by default so you can leave out the clickable: true
Related
I have a layer with two polylines and polylineDecorators. I would like to highlight both polylines and polylineDecorators when I hover on any of these. Right now I'm able to highlight only one at the time when hovering on it.
Here is my code:
var layer_migration = L.layerGroup({
layerName: 'layer_migration',
pane: 'linesPane',
});
function onEachFeature_migration (feature, layer) {
var polyline = L.polyline(layer.getLatLngs(),{
color: "#8B0000",weight: 5,opacity: 0.4,dashArray: '8,8',
dashOffset: 0
}).addTo(layer_migration);
var PLdecorator1 = L.polylineDecorator(polyline, {
patterns: [{
offset: '104%',
repeat: 100,
symbol: L.Symbol.arrowHead({pixelSize: 16,
pathOptions: {color: "#8B0000",fillOpacity: 0.6,weight: 0
}
})
}]
}).addTo(layer_migration)
var myfeatures = L.featureGroup([polyline,PLdecorator1]).addTo(layer_migration);
myfeatures.on('mouseover', function(e) {
var layer = e.target;
layer.setStyle({color: '#8B0000',opacity: 1,fillOpacity:1
});
});
}
Any help super appreciated.
Thanks,
P
In your mouseover callback, I think that e.target will just refer to the individual layer (polyline or decorator) that triggered the event, not the collection of layers that make up the feature group. I've not tested it, but according to the docs, you ought to be able to get the effect you want by calling .setLayer() on the feature group itself:
myfeatures.on('mouseover', function(e) {
myfeatures.setStyle({color: '#8B0000',opacity: 1,fillOpacity:1});
});
Also, if the two polylines are created by two separate calls to onEachFeature_migration(), then they will end up as two separate feature groups. To get around this, you might need to assign an empty featureGroup to myfeatures outside the function, then add the new polylines to it inside the function using myfeatures.addLayer().
I am exporting Google Directions routes as KML and displaying them on a Mapbox map by reading them with Omnivore and adding them to the map,
The Google KML stores each route as two Places (the start and end points) and one LineString (the route). In Mapbox I would like to show only the routes, that is to filter out the markers somehow. I'm displaying markers out of my own database and the Google markers clutter it up.
Here is my code. I change the styling of the LineStrings just to show that I can, but do not know what magic call(s) to make to not display the Points.
Thanks.
runLayer = omnivore.kml('data/xxxx.kml')
.on('ready', function() {
var llBnds = runLayer.getBounds();
map.fitBounds(llBnds);
this.eachLayer(function (layer) {
if (layer.feature.geometry.type == 'LineString') {
layer.setStyle({
color: '#4E3508',
weight: 4
});
}
if (layer.feature.geometry.type == 'Point') {
//
// Do something useful here to not display these items!!
//
}
});
})
.addTo(map);
Welcome to SO!
Many possible solutions:
Most straight forward from the code you provided, just use the removeLayer method on your runLayer Layer Group when you get a 'Point' feature.
Cleaner solution would be to filter out those features before they are even converted into Leaflet layers, through a custom GeoJSON Layer Group passed as 3rd argument of omnivore.kml, with a specified filter option:
var customLayer = L.geoJSON(null, {
filter: function(geoJsonFeature) {
// my custom filter function: do not display Point type features.
return geoJsonFeature.geometry.type !== 'Point';
}
}).addTo(map);
var runLayer = omnivore.kml('data/xxxx.kml', null, customLayer);
You can also use the style and/or onEachFeature options on customLayer to directly apply your desired style on your LineString.
Using leaflet is there any way I can get the bounds (NorthEast, SouthWest) of a loaded tile? I want to request the server to load the markers only for a particular tile which is loaded, so that when user is panning/dragging the map he can easily see the new markers on new area.
What you really want to do is a subclass of L.GridLayer. This will allow fine control over loaded/unloaded tiles, and is the best way to use the private L.GridLayer._tileCoordsToBounds() method.
With some basic handling of loaded markers, it should look like:
L.GridLayer.MarkerLoader = L.GridLayer.extend({
onAdd: function(map){
// Add a LayerGroup to the map, to hold the markers
this._markerGroup = L.layerGroup().addTo(map);
L.GridLayer.prototype.onAdd.call(this, map);
// Create a tilekey index of markers
this._markerIndex = {};
},
onRemove: function(map){
this._markergroup.removeFrom(map);
L.GridLayer.prototype.onRemove.call(this, map);
};
createTile: function(coords, done) {
var tileBounds = this._tileCoordsToBounds(coords);
var tileKey = this._tileCoordsToKey(coords);
var url = ...; // Compute right url using tileBounds & coords.z
fetch(url).then(function(res){
if (!key in this._markerIndex) { this._markerIndex[key] = []; }
// unpack marker data from result, instantiate markers
// Loop as appropiate
this._markerGroup.addLayer(marker);
this._markerIndex[key] = marker;
done(); // Signal that the tile has been loaded successfully
});
},
_removeTile: function (key) {
for (var i in this._markerIndex[key]) {
this._markerGroup.remove(this._markerIndex[key][i]);
}
L.GridLayer.prototype._removeTile.call(this, key);
}
});
Please note that zooming might be a source of bugs and graphical glitches (markers being removed because a tile unloads, before the markers at the new zoom level are loaded). Beware of that.
I'm using Mapbox with Leaflet for drawing, editing and removing polygons etc. Every time I create a new polygon, I convert them to a GeoJSON layer and then add it to the featureGroup that I created, because I want to associate each layer with an ID property that I can use later. This is what I have:
var featureGroup = L.featureGroup().addTo(map);
var drawControl = new L.Control.Draw({
edit: {
featureGroup: featureGroup
},
draw: {
polygon: {
allowIntersection: false
},
polyline: false,
rectangle: false,
circle: false,
marker: false
}
}).addTo(map);
map.on('draw:created', addPolygon);
map.on('draw:edited', editPolygon);
map.on('draw:deleted', deletePolygon);
function deletePolygon(e) {
featureGroup.removeLayer(e.layer);
}
function editPolygon(e) {
featureGroup.eachLayer(function (layer) {
layer.eachLayer(function (layer) {
addPolygon({ layer: layer });
});
});
}
function addPolygon(e) {
var geojsonstring = e.layer.toGeoJSON();
var geojson = L.geoJson(geojsonstring);
featureGroup.addLayer(geojson);
}
When I do this, creating polygons is not a problem. But when I try to edit or delete polygons, it doesn't work properly.
When I try to edit a polygon, it tells me "TypeError: i.Editing is undefined". It doesn't allow me to cancel editing as well.
When I try to delete a polygon, it is not displayed in the map anymore, but it is still not removed from the featureGroup.
What am I doing wrong here?
Edit: The way I'm currently doing this is the same way that ghybs has suggested. But the problem is, once all the edits are done, the polygons are saved to a database (I convert it to a WKT string to save in SQLServer). And when the page is loaded the next time, the polygons are loaded back from the database, and the user can edit or delete them and save it back to the database.
As it is right now, when the user makes the edit and saves them again, it only creates duplicate polygons. and I don't know of any way to connect the edited polygons to the ones from the database.
So I thought if I could convert them to GeoJSON and assign an ID property to each layer (something like ID=0 if it is a new layer, and the corresponding polygonID from the database if it is loaded from the database). So that when they are saved again, I can check this ID and based on that, I can either update the available polygon, or create a new polygon in the database.
Is there a better way of doing this?
Not sure exactly why in addPolygon you go through a GeoJSON object that you convert back into a Leaflet layer group through L.geoJson?
You could have directly added the created layer, as in Leaflet.draw "draw:created" example:
function addPolygon(e) {
var layer = e.layer;
var feature = layer.feature = layer.feature || {}; // Initialize layer.feature
// use the feature.id: http://geojson.org/geojson-spec.html#feature-objects
feature.id = 0; // you can change it with your DB id once created.
featureGroup.addLayer(layer);
// record into DB, assuming it returns a Promise / Deferred.
recordToDb(layer.toGeoJSON()).done(function (result) {
feature.id = result.id; // Update the layer id.
});
}
As for the reason for you error, it is due to the fact that you add a (GeoJSON) Layer Group to your featureGroup, which Leaflet.draw plugin does not know how to handle. You must add only "non group" layers.
See also: https://gis.stackexchange.com/questions/203540/how-to-edit-an-existing-layer-using-leaflet
I'm still learning and I'm a bit stuck. I may be trying to do to much at once. I have a MapBox map working great with a clickable layer menu taken from examples on the MapBox site. I also have a MarkerClusterGroup which also works and is always visible on the map. Is there a way I could somehow have the MarkerClusterGroup clickable on/off just like layers identified in var overlays = { ...
Below is the code that I think needs the help:
var layers = {
Streets: L.mapbox.tileLayer('mapbox.streets').addTo(map),
Satellite: L.mapbox.tileLayer('mapbox.satellite'),
Light: L.mapbox.tileLayer('mapbox.light'),
};
var overlays = {
DataA: L.mapbox.featureLayer().loadURL('/data/ctsnew.geojson'),
DataB: L.mapbox.featureLayer().loadURL('/data/selectZipcodes.geojson'),
};
// Since featureLayer is an asynchronous method, we use the `.on('ready'`
// call to only use its marker data once we know it is actually loaded.
Markers: L.mapbox.featureLayer('examples.map-h61e8o8e').on('ready', function(e) {
// The clusterGroup gets each marker in the group added to it
// once loaded, and then is added to the map
var clusterGroup = new L.MarkerClusterGroup();
e.target.eachLayer(function(layer) {
clusterGroup.addLayer(layer);
});
map.addLayer(clusterGroup);
});
Could be something as simple as misuse of brackets. Thanks in advance.
You have to include your Marker Cluster Group in your overlays object. For example you could instantiate it just before defining overlays, even if your Cluster Group is empty for now.
Then you fill it once it has downloaded its data.
var layers = {
Streets: L.mapbox.tileLayer('mapbox.streets').addTo(map),
Satellite: L.mapbox.tileLayer('mapbox.satellite'),
Light: L.mapbox.tileLayer('mapbox.light'),
};
var clusterGroup = L.markerClusterGroup();
var overlays = {
DataA: L.mapbox.featureLayer().loadURL('/data/ctsnew.geojson'),
DataB: L.mapbox.featureLayer().loadURL('/data/selectZipcodes.geojson'),
Markers: clusterGroup
};
// Since featureLayer is an asynchronous method, we use the `.on('ready'`
// call to only use its marker data once we know it is actually loaded.
L.mapbox.featureLayer('examples.map-h61e8o8e').on('ready', function(e) {
// The clusterGroup gets each marker in the group added to it
// once loaded, and then is added to the map
e.target.eachLayer(function(layer) {
clusterGroup.addLayer(layer);
});
map.addLayer(clusterGroup); // use that line if you want to automatically add the cluster group to the map once it has downloaded its data.
});