update properties of geojson to use it with leaflet - leaflet

I have a requirement of using leaflet.js to add a map to my site. The site has an administration view where an admin can add markers and add description and image to each marker.
I used the leaflet.draw plugin, and on the create event I try to update the GeoJSON object I got using event.layer.toGeoJSON() to add some properties like image and text but with no luck.
Can any one help me on this?
var osmUrl = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
osmAttrib = '© OpenStreetMap contributors',
osm = L.tileLayer(osmUrl, {
maxZoom: 18,
attribution: osmAttrib
});
map = new L.Map('map', {
layers: [osm],
center: new L.LatLng(31.9500, 35.9333),
zoom: 15
}),
drawnItems = L.geoJson().addTo(map);
map.addControl(new L.Control.Draw({
edit: {
featureGroup: drawnItems
}
}));
map.on('draw:created', function(event) {
var layer = event.layer;
var json = event.layer.toGeoJSON();
json.properties.desc = null;
json.properties.image = null;
drawnItems.addLayer(L.GeoJSON.geometryToLayer(json));
addPopup(layer);
});
function addPopup(layer) {
var content = '<input id="markerDesc" type="text"/ onblur="saveData(layer);">';
layer.bindPopup(content).openPopup();
alert('out');
}
function saveData(layer) {
var markerDesc = $('#markerDesc').val();
var json = layer.toGeoJSON();
json.feature.properties.desc = markerDesc;
}

There is no need in your "draw:created" listener to convert into GeoJSON and then back to a layer.
By the way, you then add a popup to layer whereas you do not do anything with it, since you transformed it into GeoJSON data and created a new layer out of that data.
Simply create the following structure, so that the stored data can be converted into GeoJSON later on (if you ever need that functionality): layer.feature.type = "Feature" and layer.feature.properties.
var osmUrl = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
osmAttrib = '© OpenStreetMap contributors',
osm = L.tileLayer(osmUrl, {
maxZoom: 18,
attribution: osmAttrib
});
map = L.map('map', {
layers: [osm],
center: [31.9500, 35.9333],
zoom: 15
});
var drawnItems = L.geoJson().addTo(map);
map.addControl(new L.Control.Draw({
edit: {
featureGroup: drawnItems
}
}));
map.on('draw:created', function (event) {
var layer = event.layer,
feature = layer.feature = layer.feature || {};
feature.type = feature.type || "Feature";
var props = feature.properties = feature.properties || {};
props.desc = null;
props.image = null;
drawnItems.addLayer(layer);
addPopup(layer);
});
function addPopup(layer) {
var content = document.createElement("textarea");
content.addEventListener("keyup", function () {
layer.feature.properties.desc = content.value;
});
layer.on("popupopen", function () {
content.value = layer.feature.properties.desc;
content.focus();
});
layer.bindPopup(content).openPopup();
}
Demo: https://jsfiddle.net/ve2huzxw/314/
Edited: previous code did not actually implemented well the GeoJSON properties functionality (was saved on geometry instead of feature, due to missing layer.feature.type = "Feature", see also Leaflet Draw not taking properties when converting FeatureGroup to GeoJson)

Related

adding layer on search for leaflet map changes polygon opacity

I have two leaflet maps which are loaded with geojson on the basis of a search button click. This shows some polygons with a style opacity 0.3 so you can see the street names under the polygons.
It works great except that any additional searches and loading of polygons starts to change the opacity of the polygon, making it more solid so you cant read the names of the streets under the polygon.
I try clearing the geojson layer before adding to the map, but the issue persists.
I have created a rough code pen of the issue here:
https://codepen.io/joomkit/pen/xxXgLPJ?editors=1111
Essentially just click the search button to load the layer no need to fill the listener runs a function and gets static data.
I have tried various methods to remove layer. A second click on the search is meant to clear the layer and load a new one. In the example it's just reloading the original data but the opacity is clearly demonstrated.
Main code is also below.
var geoMap2;
var lamap = new L.Map("map2", {
center: new L.LatLng(51.44094723464765, 0.048892332250943187),
// center: new L.LatLng(39.75621,-104.99404),
zoom: 14,
maxZoom: 18
});
var osm2 = L.tileLayer(
"https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png",
{
attribution:
'© OpenStreetMap contributors © CARTO',
subdomains: "abcd",
maxZoom: 18
}
);
lamap.addLayer(osm2);
searchButton.addEventListener("click", function (e) {
let searchQuery = inputSearch.value;
// searchOpenFunding(searchQuery);
setLaMap(data);
});
function setLaMap(data) {
removeLayers();
let geojsonFeatureCollection2 = {
type: "FeatureCollection",
features: setFeatureCollection2(data)
};
geoMap2 = L.geoJSON(geojsonFeatureCollection2, {
style: polyStyleLAMap,
onEachFeature: function myonEachFeatureLaMap(feature, layer) {
layer.myTag = "myGeoJSON";
}
}).addTo(lamap);
lamap.setMaxBounds(lamap.fitBounds(geoMap2.getBounds()));
lamap.setZoom(13);
}
var removeLayers = function () {
lamap.eachLayer(function (layer) {
if (layer.myTag && layer.myTag === "myGeoJSON") {
lamap.removeLayer(layer);
console.log("rem layer from ");
}
});
};
function setFeatureCollection2(data) {
for (const [key, item] of Object.entries(data)) {
// setup lealfet geojson collection from data mapit api is incomplete pe ritem so we build it here
geoJsonFeatures2.push({
type: "Feature",
properties: {
craftentryId: item.id,
areaId: item.mapitAreaId,
lsoacode: item.lsoacode,
localauthority: item.localauthority,
openForFunding: item.openForFunding,
fundableRegion: item.fundableRegion,
title: item.title,
popupContent: ""
},
geometry: item.geojson
});
}
return geoJsonFeatures2;
}
function polyStyleLAMap(feature) {
return {
fillColor: getFillColorLaMap(feature.properties.openForFunding),
weight: 1,
opacity: 1,
color: getBorderColor(feature.properties.openForFunding),
dashArray: "0",
fillOpacity: 0.3
};
}
function getFillColorLaMap(d) {
return d === true ? "#FFFFFF" : d === false ? "#FED976" : "#FED976";
}
function getBorderColor(d) {
return d === true ? "#0e9c12" : d === false ? "#adabab" : "#cccccc";
}
Look here https://codepen.io
The code I modified is:
// 029A, 029C
function setLaMap(data) {
removeLayers();
let geojsonFeatureCollection2 = {
type: "FeatureCollection",
features: setFeatureCollection2(data),
};
console.log(geojsonFeatureCollection2);
const geoMap2 = L.geoJSON(geojsonFeatureCollection2, {
style: polyStyleLAMap,
onEachFeature: function (feature, layer) {
layer.myTag = "myGeoJSON";
},
});
map.addLayer(geoMap2);
map.setMaxBounds(map.fitBounds(geoMap2.getBounds()));
// map.setZoom(13);
}
function removeLayers() {
map.eachLayer((layer) => {
if (layer.myTag && layer.myTag === "myGeoJSON") {
console.log(layer);
map.removeLayer(layer);
}
});
}
You need to keep the geojson layer in a global variable and remove it before over writing with the new layer data.
var geoMap2;
function setLaMap(data){
if(geoMap2) { // if geoMap2 is set remove it
lamap.removeLayer(geoMap2)
}
const geojsonFeatureCollection2 = {
"type": "FeatureCollection",
"features": setFeatureCollection2(data)
}
geoMap2 = L.geoJSON(geojsonFeatureCollection2, {
style: polyStyleLAMap,
onEachFeature: myonEachFeatureLaMap
})
.........

Leaflet l.layergroup not displaying second layer (features)

I'm trying to make a map of the Rockies that has multiple layers with different geological features. The first layer, with hot springs, works perfectly well--each spring has a corresponding marker and pop-up that displays the spring's name and state.
The problem comes when I try to add a second layer with a different geological feature (canyons and gorges). I have used the code from the "hot springs" layer, but it isn't getting added to the map, even when I select it from the layer control.
I don't think the problem is the geoJSON file. (The points will display properly on the map when I look at the file where it is stored on GitHub, which is the canyonsURL.)
Here is the code for the part that isn't working:
var canyons = new L.LayerGroup();
d3.json(canyonsURL, function(data) {
createFeatures2(data.features);
});
function createFeatures2(canyonsURL) {
function onEachFeature(feature, layer) {
layer.bindPopup(feature.properties.Canyon +
"<hr><p>" + feature.properties.State);
}
function style(feature, layer) {
return {
opacity: 0.5,
radius: 5,
weight: 1,
color: "black",
fillColor: "yellow",
fillOpacity: 0.5
}
}
var canyon = d3.json(canyonsURL, {
pointToLayer: function(_geometry, coordinates) {
return L.circleMarker(coordinates);
},
onEachFeature: onEachFeature,
style: style
}).addTo(canyons);
createMap(canyons)
}
I have tried both with and without the final createMap(canyons). I have tried using identical names for functions (instead of adding the 2) to the end.
I'm sure it's right in front of my eyes and I just can't see it. The same code (obviously pointing to a different URL and with slightly different styling) is working fine for the hot springs. What did I mess up on?
EDIT: This is going to get long; sorry. Here is the createMap function:
function createMap() {
var satellite = L.tileLayer("https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}", {
attribution: "Map data © Openelevationmap contributors, CC-BY-SA, Imagery © Mapbox",
maxZoom: 18,
id: "mapbox.satellite",
accessToken: API_KEY
});
var pirates = L.tileLayer("https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}", {
attribution: "Map data © Openelevationmap contributors, CC-BY-SA, Imagery © Mapbox",
maxZoom: 18,
id: "mapbox.pirates",
accessToken: API_KEY
});
var terrain = L.tileLayer("https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}", {
attribution: "Map data © Openelevationmap contributors, CC-BY-SA, Imagery © Mapbox",
maxZoom: 18,
id: "mapbox.mapbox-terrain-v2",
accessToken: API_KEY
});
var terrain_rgb = L.tileLayer("https://api.mapbox.com/v4/mapbox.terrain-rgb/{z}/{x}/{y}.pngraw?access_token={accessToken}", {
attribution: "Map data © Openelevationmap contributors, CC-BY-SA, Imagery © Mapbox",
maxZoom: 18,
id: "mapbox.mapbox-terrain-rgb",
accessToken: API_KEY
});
// Define a baseMaps object to hold our base layers
var baseMaps = {
// "Elevation Map": elevationmap,
"Satellite": satellite,
"Terrain": terrain,
"Treasure": pirates,
"Shadow": terrain_rgb
};
// Create overlay object to hold our overlay layer
var overlayMaps = {
"Hot Springs": springs,
"Canyons": canyons
};
// Create our map, giving it the satellite and hotSprings layers to display on load
var myMap = L.map("map", {
center: [
44.2643, -109.7879
],
zoom: 5,
layers: [satellite, springs]
});
// Create a layer control
// Pass in our baseMaps and overlayMaps
// Add the layer control to the map
L.control.layers(baseMaps, overlayMaps, {
collapsed: true,
position: 'bottomright'
}).addTo(myMap);
}
Here is the code that I used to (successfully) add the hot springs:
// Create layers for layergroup
var springs = new L.LayerGroup();
d3.json(springsURL, function(data) {
// Once we get a response, send the data.features object to the createFeatures function
createFeatures(data.features);
});
function createFeatures(springsURL) {
// Define a function we want to run once for each feature in the features array
// Give each feature a popup with name and state of spring
function onEachFeature(feature, layer) {
layer.bindPopup(feature.properties.SpringName +
"<hr><p>" + feature.properties.State);
}
function style(feature, layer) {
return {
opacity: 0.5,
radius: 5,
weight: 1,
color: "black",
fillColor: "red",
fillOpacity: 0.5
}
}
var hotSprings = L.geoJSON(springsURL, {
pointToLayer: function(_geometry, coordinates) {
return L.circleMarker(coordinates);
},
onEachFeature: onEachFeature,
style: style
}).addTo(springs);
// Sending our hotSprings layer to the createMap function
createMap(springs);
}
your function createMap(springs); makes no sense. If you add a parameter to the function then you have to read it out. function createMap() { ... } it should be function createMap(layer) { ... }. But that is not necessary for you.
your script should look like this:
var myMap; //the var should init global so you can call it from everywhere
var springs = L.featureGroup(); //Same as L.layerGroup() but with more options.
var canyons = L.featureGroup();
function createMap(){
//...
myMap = L.map("map", { //<-- without "var"
center: [
44.2643, -109.7879
],
zoom: 5,
layers: [satellite, springs, canyons] //<--- Add canyons
});
//...
}
createMap();
var style1 = function style(feature) {
return {
opacity: 0.5,
radius: 5,
weight: 1,
color: "black",
fillColor: "red",
fillOpacity: 0.5
}
}
var style2 = function style(feature) {
return {
opacity: 0.5,
radius: 5,
weight: 1,
color: "yellow",
fillColor: "red",
fillOpacity: 0.5
}
}
function createFeatures(springsURL,stlyecallback, layer) {
// Define a function we want to run once for each feature in the features array
// Give each feature a popup with name and state of spring
function onEachFeature(feature, layer) {
layer.bindPopup(feature.properties.SpringName +
"<hr><p>" + feature.properties.State);
}
var hotSprings = L.geoJSON(springsURL, {
pointToLayer: function(_geometry, coordinates) {
return L.circleMarker(coordinates);
},
onEachFeature: onEachFeature,
style: stlyecallback
}).addTo(layer);
}
d3.json(springsURL, function(data) {
createFeatures(data.features, style1, springs);
});
d3.json(springsURL, function(data) {
createFeatures(data.features, style2, canyons);
});
I hope I have no typo...

make multiple markers draggable

I use an Array to create a couple of markers in my leaflet map for Photos with Geolocations which works fine:
for (var p of ArrayofData) {
var lat = p.lat;
var lon = p.lon;
var markerLocation = new L.LatLng(lat, lon);
var marker = new L.Marker(markerLocation,{
draggable: 'true',
id: p.Filename
});
mymap.addLayer(marker);
}
to enable users to change their photo location I need them to drag those markers around and then I can read the new location:
marker.on('dragend', function (e) {
// Get position of dropped marker
var latLng = e.target.getLatLng();
console.log ("id:"+e.target.options.id);
console.log ("NewLocation:"+latLng);
});
As all of my markers have the same constructor it seems as if this script only works with the last marker. All others are draggable but do not give back any feedback when released.
Does anybody know, how I can access all of them?
You can do that by adding those drag event handlers inside your for loop.
for (var p of data) {
var lat = p.lat;
var lon = p.lon;
var markerLocation = new L.LatLng(lat, lon);
var marker = new L.Marker(markerLocation,{
draggable: 'true',
id: p.Filename
});
map.addLayer(marker);
marker.on('dragend', function (e) {
// Get position of dropped marker
var latLng = e.target.getLatLng();
console.log ("id:"+e.target.options.id);
console.log ("NewLocation:"+latLng);
});
}
Also I highly recommend that you keep track of your markers by adding them to an array.
var markers = [];
for (var p of data) {
var lat = p.lat;
var lon = p.lon;
var markerLocation = new L.LatLng(lat, lon);
var marker = new L.Marker(markerLocation,{
draggable: 'true',
id: p.Filename
});
map.addLayer(marker);
marker.on('dragend', function (e) {
// Get position of dropped marker
var latLng = e.target.getLatLng();
console.log ("id:"+e.target.options.id);
console.log ("NewLocation:"+latLng);
});
markers.push(marker);
}
Demo

Open the popups of all markers that are visible on the map with a zoom > 10

I need to open all the markers that are visible on the map at Zoom,
10. I also use leaflet.markercluster.
Init map:
initMap() {
this.map = L.map('map', {
center: [55.55, 37.61],
zoom: 9,
layers: this.layer
})
this.tileLayer = L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 18,
attribution:
'© OpenStreetMap, ©'
})
this.tileLayer.addTo(this.map)
this.map.on('zoom', function(ev) {
???
})
Add marker layer:
this.markerLayer = new L.LayerGroup() // layer contain searched elements
// this.map.addLayer(this.markerLayer)
for (const i in data) {
...
const marker = new L.Marker(new L.latLng(loc), { title: title, icon: icon })// se property searched
marker.bindPopup(title)
this.markerLayer.addLayer(marker)
}
Use leaflet marker cluster:
this.markersLayer = L.markerClusterGroup({
iconCreateFunction: function(cluster) { ... },
singleMarkerMode: false
})
this.markersLayer.addLayer(this.markerLayer)
this.map.addLayer(this.markersLayer)
You should add your markers to an array before / after adding them to the map to access them easily.
var markers = [];
for (const i in data) {
const marker = new L.Marker(new L.latLng(loc), { title: title, icon: icon })
marker.bindPopup(title)
this.markerLayer.addLayer(marker)
markers.push(marker);
}
after that you can just loop through your markers array and use openPopUp function of marker to open popups of your markers programmatically.
for(i = 0; i< markers.length;i++){
markers[i].openPopup();
}

circleMarker click not getting called - OverlappingMarkerSpiderfier-Leaflet

I see that the click event doesn't get called for circleMarker, but does get called for normal Marker. Any help? https://github.com/jawj/OverlappingMarkerSpiderfier-Leaflet/issues/19
http://jsfiddle.net/abarik/crk3jrhp/2/
html
<div id="map"></div>
css
#map {
height: 440px;
}
javascript
map = L.map('map', {
center: [7.2, 40.9],
zoom: 2
});
L.tileLayer('http://{s}.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.png', {
attribution: "Map: Tiles Courtesy of MapQuest (OpenStreetMap, CC-BY-SA)",
subdomains: ["otile1", "otile2", "otile3", "otile4"],
maxZoom: 12,
minZoom: 2
}).addTo(map);
var oms = new OverlappingMarkerSpiderfier(map);
var popup = new L.Popup();
oms.addListener('click', function(marker) {
popup.setContent(marker.__name);
popup.setLatLng(marker.getLatLng());
map.openPopup(popup);
});
var marker1 = new L.Marker([1, 1]);
marker1.__name = 'marker1'
map.addLayer(marker1);
oms.addMarker(marker1);
var marker2 = new L.Marker([1, 1]);
marker2.__name = 'marker2'
map.addLayer(marker2);
oms.addMarker(marker2);
var marker1 = new L.circleMarker([20, 20]);
marker1.__name = 'cirmarker1'
map.addLayer(marker1);
oms.addMarker(marker1);
var marker2 = new L.circleMarker([20, 20]);
marker2.__name = 'cirmarker2'
map.addLayer(marker2);
oms.addMarker(marker2);