'Invalid GeoJSON object.' Using Leaflet and leaflet-ajax - javascript

I'm using leaflet to build a map of France, with the regions highlighted, and on-click zoom.
I used this tutorial first: http://leafletjs.com/examples/choropleth.html
First, I've had the geojson in the script.js, but in my case i needed regions in separated geojson files.
So i'm now calling them in the script.js with leaflet-ajax like this :
var BordeauxLayer = new L.GeoJSON.AJAX("src/js/DI_Bordeaux.geojson").addTo(map);
The regions is displayed on the map, but now all the function to zoom-in, highlight etc... that I took on the tutorial don't work anymore.
L.geoJson(BordeauxLayer,{onEachFeature: onEachFeature}).addTo(map);
// HIGHLIGHT FEATURE = MOUSEOVER
function onEachFeature(feature, layer) {
layer.on({
mouseover: highlightFeature,
mouseout: resetHighlight,
click: zoomToFeature
});
}
// HIGHLIGHT FEATURE = MOUSEOVER
function highlightFeature(e) {
var layer = e.target;
layer.setStyle({
weight: 5,
color: '#666',
dashArray: '',
fillOpacity: 0.7
});
};
// HIGHLIGHT FEATURE = MOUSE LEFT
function resetHighlight(e) {
geojson.resetStyle(e.target);
};
// ZOOM TO THE REGION
function zoomToFeature(e) {
map.fitBounds(e.target.getBounds());
}
And now the console says " Uncaught Error: Invalid GeoJSON object. " on the line 8 of leaflet.js.
The problem seems to be on this line :
L.geoJson(BordeauxLayer,{onEachFeature: onEachFeature}).addTo(map);
And I don't get why :(
Edit : Here is my geojson : https://api.myjson.com/bins/3s1ad
Thank's in advance.

Your call to onEachFeature has to be in your AJAX call
var BordeauxLayer = new L.GeoJSON.AJAX("src/js/DI_Bordeaux.geojson", {onEachFeature: onEachFeature}).addTo(map);
You also have to get rid of
L.geoJson(BordeauxLayer,{onEachFeature: onEachFeature}).addTo(map);

Related

Leaflet move single point of polygon in GeoJSON layer

I have leaflet with a geoJSON layer group and load several geoJSON features, each as a separate layer added to the geoJSON layer group. For a given selected layer, I need to move a point of the polygon on that layer using javascript. So, for example, I may need to move the 3rd vertex to 30.123, -80.123. I cannot figure out how to do this. I can move a marker easily with the setLatLng() method but I can't find anything to change a polygon point.
Here is an example of how I am creating the map and adding my geoJSON features:
function createMap(){
myMap = L.map('locationMap', {
editable: true,
attributionControl: false,
fullscreenControl: true,
fullscreenControlOptions: {
position: 'topleft'
}
}).setView([#Model.MapCenterLat, #Model.MapCenterLong], #Model.MapInitialZoom);
L.tileLayer('#Model.MapUrl2', {
drawControl: true,
maxZoom: 20,
id: 'mapbox.streets'
}).addTo(myMap);
geoJsonLayer = L.geoJson().addTo(myMap);
loadGeoFences('');
}
function loadGeoFences(parentId) {
var url = '#Url.Action("GetGeoFences")';
$.get(url, { parentId: parentId },
function (data) {
if (data.length > 0) {
$.each(data, function (index, value) {
var newLayer = L.geoJson(value,
{
onEachFeature: applyLayerStyle
});
newLayer.addTo(geoJsonLayer);
});
}
});
}
I was able to do this using the leaflet.editing plugin. Once you have the correct layer, the layer.editing.latlngs array can be modified with the desired coordinates. Then call layer.redraw() to update the polygon.
You can change the latlngs while geoJson loading with following:
function onEachFeature(feature, layer) {
if(layer instanceof L.Polyline){
var latlngs = layer.getLatLngs()
var ll = latlngs[0][2];
ll.lat = 51.490056
latlngs[0][2] = ll;
layer.setLatLngs(latlngs);
}
}
L.geoJSON(json,{onEachFeature: onEachFeature}).addTo(map);
https://jsfiddle.net/falkedesign/hvdxo3z7/

Best way to convert Leaflet Geojson layers to Leaflet rectangle vector [duplicate]

I am trying to use leaflet's edit function on polygons that I loaded from my database. When I click on leaflet's edit button I get the error
Cannot read property 'enable' of undefined
This thread describes a similar problem, and user ddproxy said
"Since FeatureGroup extends LayerGroup You can walk through the layers
presented and add them individually to the FeatureGroup used for
Leaflet.draw"
I am confused what he means by "walk through", I thought I was adding a layer group, so i'm not sure what I would be walking through. Does this have to do with the fact that i'm adding the polygons as a geoJSON object? Adding the polygons to the map, binding their popups, and assigning them custom colors works perfectly FYI.
The following is the relevant code:
<script>
window.addEventListener("load", function(event){
//other stuff
loadHazards();
});
//next 6 lines siply add map to page
var osmUrl = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
var osmAttrib = '© OpenStreetMap contributors'
var osm = L.tileLayer(osmUrl, { maxZoom: 18, attribution: osmAttrib})
var map = new L.Map('map', { center: new L.LatLng(39.255467, -76.711964), zoom: 16 })
osm.addTo(map);
var drawnItems = L.featureGroup().addTo(map);
var Hazards = L.featureGroup().addTo(map);
L.control.layers({
'osm': osm.addTo(map)
},
{
'drawlayer': drawnItems,
"Hazards" : Hazards,
"Tickets": Tickets
},
{
position: 'topleft', collapsed: false
}
).addTo(map);
map.addControl(new L.Control.Draw({
edit: {
featureGroup: Hazards,
poly: {
allowIntersection: false
}
},
draw: {
polygon: {
allowIntersection: false,
showArea: true
},
rectangle:false,
circle:false,
circlemarker:false
}
}));
map.on(L.Draw.Event.CREATED, function (event) {
var layer = event.layer;
drawnItems.addLayer(layer);
});
</script>
And the loadHazards() function:
function loadHazards(){
$.ajax({
type: 'GET',
url:'/loadPolygonFromDatabase',
success : function(polygons){
polygons = JSON.parse(polygons);
var toAdd = [];
for (i in polygons){
var item = {
"type" : "Feature",
"properties":{
"category":"",
"description":"",
"ID":""
},
"geometry" : {
"type":"Polygon",
"coordinates":[],
}
};
item["geometry"]["coordinates"][0] = polygons[i]["coordinates"];
item["properties"]["category"] = polygons[i]["category"];
item["properties"]["description"] = polygons[i]["description"];
item["properties"]["ID"] = polygons[i]["ID"];
toAdd.push(item);
}
//Add information to popup
var layerGroup = L.geoJSON(toAdd, {
onEachFeature: function (feature, layer) {
layer.bindPopup( '<h1>' + feature.properties.category + '</h1>'
+ '<p>' + feature.properties.description + '</p>');
layer.id = feature.properties.ID;
},
style: function(feature){
switch (feature.properties.category) {
case 'Rabid_Beavers': return {color: "#663326"};
case 'Fire': return {color: "#ff0000"};
case 'Flood': return {color: "#0000ff"};
}
}
}).addTo(Hazards);
}
});
}
Thanks in advance!
As mentioned by #ghybs Leaflet.Draw doesn't support Groups or MultiPolygons. I needed the same functionality so a few years ago I created Leaflet-Geoman (previously named leaflet.pm) which supports holes, MultiPolygons, GeoJSON and LayerGroups:
https://github.com/geoman-io/leaflet-geoman
Hope it helps.
Unfortunately Leaflet.draw plugin does not handle nested Layer Groups (same for Feature Groups / GeoJSON Layer Groups).
That is the meaning of the Leaflet.draw #398 issue you reference: they advise looping through the child layers of your Layer/Feature/GeoJSON Layer Group (e.g. with their eachLayer method). If the child layer is a non-group layer, then add it to your editable Feature Group. If it is another nested group, then loop through its own child layers again.
See the code proposed in that post:
https://gis.stackexchange.com/questions/203540/how-to-edit-an-existing-layer-using-leaflet
var geoJsonGroup = L.geoJson(myGeoJSON);
addNonGroupLayers(geoJsonGroup, drawnItems);
// Would benefit from https://github.com/Leaflet/Leaflet/issues/4461
function addNonGroupLayers(sourceLayer, targetGroup) {
if (sourceLayer instanceof L.LayerGroup) {
sourceLayer.eachLayer(function(layer) {
addNonGroupLayers(layer, targetGroup);
});
} else {
targetGroup.addLayer(sourceLayer);
}
}
In your very case, you can also refactor your code with 2 other solutions:
Instead of building your layerGroup (which is actually a Leaflet GeoJSON Layer Group) first and then add it into your Hazards Feature Group, make the latter a GeoJSON Layer Group from the beginning, and addData for each of your single Features (item):
var Hazards = L.geoJSON(null, yourOptions).addTo(map);
for (i in polygons) {
var item = {
"type" : "Feature",
// etc.
};
// toAdd.push(item);
Hazards.addData(item); // Directly add the GeoJSON Feature object
}
Instead of building a GeoJSON Feature Object (item) and parse it into a Leaflet GeoJSON Layer, you can directly build a Leaflet Polygon and add it into your Hazards Layer/Feature Group:
for (i in polygons) {
var coords = polygons[i]["coordinates"];
var style = getStyle(polygons[i]["category"]);
var popup = ""; // fill it as you wish
// Directly build a Leaflet layer instead of an intermediary GeoJSON Feature
var itemLayer = L.polygon(coords, style).bindPopup(popup);
itemLayer.id = polygons[i]["ID"];
itemLayer.addTo(Hazards);
}
function getStyle(category) {
switch (category) {
case 'Rabid_Beavers': return {color: "#663326"};
case 'Fire': return {color: "#ff0000"};
case 'Flood': return {color: "#0000ff"};
}
}

Leaflet Draw "Cannot read property 'enable' of undefined" adding control to geoJSON layer

I am trying to use leaflet's edit function on polygons that I loaded from my database. When I click on leaflet's edit button I get the error
Cannot read property 'enable' of undefined
This thread describes a similar problem, and user ddproxy said
"Since FeatureGroup extends LayerGroup You can walk through the layers
presented and add them individually to the FeatureGroup used for
Leaflet.draw"
I am confused what he means by "walk through", I thought I was adding a layer group, so i'm not sure what I would be walking through. Does this have to do with the fact that i'm adding the polygons as a geoJSON object? Adding the polygons to the map, binding their popups, and assigning them custom colors works perfectly FYI.
The following is the relevant code:
<script>
window.addEventListener("load", function(event){
//other stuff
loadHazards();
});
//next 6 lines siply add map to page
var osmUrl = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
var osmAttrib = '© OpenStreetMap contributors'
var osm = L.tileLayer(osmUrl, { maxZoom: 18, attribution: osmAttrib})
var map = new L.Map('map', { center: new L.LatLng(39.255467, -76.711964), zoom: 16 })
osm.addTo(map);
var drawnItems = L.featureGroup().addTo(map);
var Hazards = L.featureGroup().addTo(map);
L.control.layers({
'osm': osm.addTo(map)
},
{
'drawlayer': drawnItems,
"Hazards" : Hazards,
"Tickets": Tickets
},
{
position: 'topleft', collapsed: false
}
).addTo(map);
map.addControl(new L.Control.Draw({
edit: {
featureGroup: Hazards,
poly: {
allowIntersection: false
}
},
draw: {
polygon: {
allowIntersection: false,
showArea: true
},
rectangle:false,
circle:false,
circlemarker:false
}
}));
map.on(L.Draw.Event.CREATED, function (event) {
var layer = event.layer;
drawnItems.addLayer(layer);
});
</script>
And the loadHazards() function:
function loadHazards(){
$.ajax({
type: 'GET',
url:'/loadPolygonFromDatabase',
success : function(polygons){
polygons = JSON.parse(polygons);
var toAdd = [];
for (i in polygons){
var item = {
"type" : "Feature",
"properties":{
"category":"",
"description":"",
"ID":""
},
"geometry" : {
"type":"Polygon",
"coordinates":[],
}
};
item["geometry"]["coordinates"][0] = polygons[i]["coordinates"];
item["properties"]["category"] = polygons[i]["category"];
item["properties"]["description"] = polygons[i]["description"];
item["properties"]["ID"] = polygons[i]["ID"];
toAdd.push(item);
}
//Add information to popup
var layerGroup = L.geoJSON(toAdd, {
onEachFeature: function (feature, layer) {
layer.bindPopup( '<h1>' + feature.properties.category + '</h1>'
+ '<p>' + feature.properties.description + '</p>');
layer.id = feature.properties.ID;
},
style: function(feature){
switch (feature.properties.category) {
case 'Rabid_Beavers': return {color: "#663326"};
case 'Fire': return {color: "#ff0000"};
case 'Flood': return {color: "#0000ff"};
}
}
}).addTo(Hazards);
}
});
}
Thanks in advance!
As mentioned by #ghybs Leaflet.Draw doesn't support Groups or MultiPolygons. I needed the same functionality so a few years ago I created Leaflet-Geoman (previously named leaflet.pm) which supports holes, MultiPolygons, GeoJSON and LayerGroups:
https://github.com/geoman-io/leaflet-geoman
Hope it helps.
Unfortunately Leaflet.draw plugin does not handle nested Layer Groups (same for Feature Groups / GeoJSON Layer Groups).
That is the meaning of the Leaflet.draw #398 issue you reference: they advise looping through the child layers of your Layer/Feature/GeoJSON Layer Group (e.g. with their eachLayer method). If the child layer is a non-group layer, then add it to your editable Feature Group. If it is another nested group, then loop through its own child layers again.
See the code proposed in that post:
https://gis.stackexchange.com/questions/203540/how-to-edit-an-existing-layer-using-leaflet
var geoJsonGroup = L.geoJson(myGeoJSON);
addNonGroupLayers(geoJsonGroup, drawnItems);
// Would benefit from https://github.com/Leaflet/Leaflet/issues/4461
function addNonGroupLayers(sourceLayer, targetGroup) {
if (sourceLayer instanceof L.LayerGroup) {
sourceLayer.eachLayer(function(layer) {
addNonGroupLayers(layer, targetGroup);
});
} else {
targetGroup.addLayer(sourceLayer);
}
}
In your very case, you can also refactor your code with 2 other solutions:
Instead of building your layerGroup (which is actually a Leaflet GeoJSON Layer Group) first and then add it into your Hazards Feature Group, make the latter a GeoJSON Layer Group from the beginning, and addData for each of your single Features (item):
var Hazards = L.geoJSON(null, yourOptions).addTo(map);
for (i in polygons) {
var item = {
"type" : "Feature",
// etc.
};
// toAdd.push(item);
Hazards.addData(item); // Directly add the GeoJSON Feature object
}
Instead of building a GeoJSON Feature Object (item) and parse it into a Leaflet GeoJSON Layer, you can directly build a Leaflet Polygon and add it into your Hazards Layer/Feature Group:
for (i in polygons) {
var coords = polygons[i]["coordinates"];
var style = getStyle(polygons[i]["category"]);
var popup = ""; // fill it as you wish
// Directly build a Leaflet layer instead of an intermediary GeoJSON Feature
var itemLayer = L.polygon(coords, style).bindPopup(popup);
itemLayer.id = polygons[i]["ID"];
itemLayer.addTo(Hazards);
}
function getStyle(category) {
switch (category) {
case 'Rabid_Beavers': return {color: "#663326"};
case 'Fire': return {color: "#ff0000"};
case 'Flood': return {color: "#0000ff"};
}
}

Style both points and multipolygons simultaneously Leaflet

I was working on an application in which you can show points on a map. To improve the application, I added the functionality the show multipolygons as well.
I've created a dropdowmenu where you can select a dataset. One is a dataset containing stores (points), and the other contains precincts (multipolygons). Everything works fine and I can show either points or polygons on the map.
Because I build the application to only show points first, I've only styled the points (see code below).
var myStyle = {
"color": "#ff7800",
"weight": 5,
"opacity": 0.65
};
window["mapDataLayer"] = L.geoJson(geojson, {
pointToLayer: function (feature, latlng) {
var markerStyle = {
fillColor: getColor(feature.properties.Fastfoodketen),
color: "#696969",
fillOpacity: 0.6,
opacity: 0.9,
weight: 1,
radius: 8
};
return L.circleMarker(latlng, markerStyle);
},
onEachFeature: function (feature, layer){
layer.on({
click: function showResultsInDiv() {
var d = document.getElementById('tab4');
d.innerHTML = "";
for (prop in feature.properties){
d.innerHTML += prop+": "+feature.properties[prop]+"<br>";
}
$('.nav-tabs a[href="#tab4"]').tab('show');
}
}); },
style: myStyle
}).addTo(map);
Now I want to style the polygons. How can I alter the code above to include polygons as well?
I thought it would be a good way was to include an if/else loop to check if the geometry was a point or a multipolygon and then direct to the appropriate styling. However, I don't know how to check if a geometry is a point/polygon.
This is covered by the Leaflet documentation on GeoJSON options (emphasis mine):
pointToLayer: A Function defining how GeoJSON points spawn Leaflet layers.[...]
style: A Function defining the Path options for styling GeoJSON lines and polygons, [...]
You can see examples of this in the Leaflet tutorials for GeoJSON

Toggle layers on and off in Leaflet (more complex scenario)

I am using jQuery's getJSON method to load external line data I've created in QGIS.
What I'm trying to do is toggle my layers on and off - simple check boxes, no radio button for the basemap. I'd also like all the layers to be off when the map is initially loaded.
My code
var map=L.map('map').setView([41.9698, -87.6859], 12);
var basemap = L.tileLayer('http://a.tile.stamen.com/toner/{z}/{x}/{y}.png',
{
//attribution: would go here
maxZoom: 17,
minZoom: 9
}).addTo(map);
//display geoJson to the map as a vector
var x = function(source, map)
{
var layers = L.geoJson(source,
{
style: function(feature){
var fillColor, side=feature.properties.side;
if (side==='Both') fillColor = '#309e2d';
else if (side==='Neither') fillColor = '#d90f0f';
else if (side==='West Only') fillColor = '#e27f14';
else if (side==='East Only') fillColor = '#2b74eb';
else if (side==='North Only') fillColor = '#eae42b';
else if (side==='South Only') fillColor = '#552d04';
else fillColor = '#f0f5f3';
return { color: fillColor, weight: 3.5, opacity: null };
},
onEachFeature: function(feature, geojson){
var popupText="<h1 class='makebold'>Border: </h1>"+feature.properties.name+"<br/>"+"<h1 class='makebold'>Which Side?: </h1>"+feature.properties.side;
geojson.bindPopup(popupText);
}
}).addTo(map);
};
$.getJSON("data/Knox.geojson", function(source){ x(source, map); });
$.getJSON("data/abc.geojson", function(source){ x(source, map); });
$.getJSON("data/xyz.geojson", function(source){ x(source, map); });
I tried assigning a variable before the L.geoJson function (var layers), and then L.control.layers(null, layers).addTo(map); That doesn't seem to work.
How does one create a layer control for multiple external geojson's that are already associated with a few callback functions (L.geoJson, style, and onEachFeature)? Thanks in advance.
EDIT:
Since you clarified that you want just the entire collection to be switched on/off, it is even more simple (and almost like what you tried by assigning your L.geoJson to var layers), but you have to take care of asynchronous processes.
To avoid this issue, you could do something like:
var myLayerGroup = L.layerGroup(), // do not add to map initially.
overlays = {
"Merged GeoJSON collections": myLayerGroup
};
L.control.layers(null, overlays).addTo(map);
function x(source, map) {
// Merge the GeoJSON layer into the Layer Group.
myLayerGroup.addLayer(L.geoJson({}, {
style: function (feature) { /* … */ },
onEachFeature: function (feature, layer) { /* … */ }
}));
}
$.getJSON("data/Knox.geojson", function(source){
x(source, map);
});
Then myLayerGroup will be gradually populated with your GeoJSON features, when they are received from the jQuery getJSON requests and they are converted by L.geoJson.
If my understanding is correct, you would like the ability to switch on/off independently each feature from your GeoJSON data?
In that case, you would simply populate your layers object while building the L.geoJson layer group, e.g. inside the onEachFeature function:
var layers = {};
L.geoJson(source, {
style: function (feature) { /* … */ },
onEachFeature: function(feature, layer){
var popupText = "<h1 class='makebold'>Border: </h1>" +
feature.properties.name + "<br/>" +
"<h1 class='makebold'>Which Side?: </h1>" +
feature.properties.side;
layer.bindPopup(popupText);
// Populate `layers` with each layer built from a GeoJSON feature.
layers[feature.properties.name] = layer;
}
});
var myLayersControl = L.control.layers(null, layers).addTo(map);
If you have more GeoJSON data to load and to convert into Leaflet layers, simply do exactly the same (adding built layer into layers in onEachFeature function) and build the Layers Control only once at the end, or use myLayersControl.addOverlay(layer).
Note: make sure to structure your code to take into account your several asynchronous processes, if you load each GeoJSON data in a separate request. Refer to jQuery Deferred object. Or simply create your Layers Control first and use the addOverlay method.
If you want them to be initially hidden from the map, simply do not add the geoJson layer to the map…
I learned a lot more about layer control in Leaflet than I expected, which is great.
#ghybs offered really helpful suggestions.
My issue was about toggling external geoJson files on and off, particularly with the getJSON jQuery method. I was trying to assign a variable within my multiple callbacks, like:
var layers=L.geoJson(source,{
{style: /*....*/},
{onEachFeature: /*....*/}}
and then just going L.control.layers(null, layers).addTo(map);
That doesn't work (why? I still can't explain-I'm quite the beginner-programmer). The way I did get this to work was by creating my style and onEachFeature functions separately, like this:
function borders (feature){
var fillColor, side=feature.properties.side;
if (side==='Both') fillColor = '#309e2d';
else if (side==='Neither') fillColor = '#d90f0f';
else if (side==='West Only') fillColor = '#e27f14';
else if (side==='East Only') fillColor = '#2b74eb';
else if (side==='North Only') fillColor = '#eae42b';
else if (side==='South Only') fillColor = '#552d04';
else fillColor = '#f0f5f3';
return { color: fillColor, weight: 3.5, opacity: null };
};
and
function popUp (feature, geojson){
var popupText="<h1 class='makebold'>
Border: </h1>"+feature.properties.name+"<br/>"+"<h1 class='makebold'>
Which Side</h1>"+feature.properties.side;geojson.bindPopup(popupText);
};
and then assigning these directly as callbacks into the getJSON method. By doing it this way, I could create a variable before "drawing" my geoJson to the map with L.geoJson(). Then I could assign the variable dynamically(?) to the layer control:
$.getJSON("data/xyz.geojson", function(source){
var xyz = L.geoJson(source, {
style: borders,
onEachFeature: popUp});
togglelayer.addOverlay(xyz, 'This name shows up on the control')});
});
I stored the variable togglelayer like this:
var togglelayer = L.control.layers(null, null,{collapsed: false}).addTo(map);
This post was also helpful: How to add two geoJSON feature collections in to two layer groups

Categories

Resources