Leaflet exporting using toGeoJSON does not automatically add properties for things like color, name, description, etc - leaflet

I am working on a project that utilizes Leaflet with a lot of tailored markers and colored lines/polygons. We are not using a GeoJSON layer in Leaflet.
I would like to export all or a subset of these objects/layers to GeoJSON and then import them back in without losing any detail like color and description.
Using the native toGeoJSON function of Leaflet, I can get GeoJSON data for anything I want on the map. Here is some code that produces GeoJSON for everything on the map:
function exportToGeoJSON() {
var allMarkersObjArray = [];
map.eachLayer(function (layer) {
allMarkersObjArray.push(layer);
});
var layerGroup = L.layerGroup(allMarkersObjArray);
var data = layerGroup.toGeoJSON();
var json = JSON.stringify(data); // Stringify the object
return json;
}
A snippet of the resulting GeoJSON created looks like this:
{
"type":"Feature",
"properties":{},
"geometry":{
"type":"Point",
"coordinates":[-75.668159,36.014897]}
}
We get the basic geometry which is nice but no properties with things like:
"properties": {
"id": 1,
"name": “Marker 1”,
"description": “This is a marker”,
"marker-size": "small",
"marker-color": "#f5a91a",
"marker-symbol": "b"
}
I want to find the best way to create these properties. I could certainly build the code to extract the properties from the Leaflet Marker object as an example but maybe there is a better way.
I have searched for potential plug-ins to do this but nothing is coming up on the radar screen. I would think other folks would like this type of capability.
Anyway, any thought or comments are welcome.

Related

GeoJson styling Asymmetrik/ngx-leaflet

Hi I have a problem with Asymmetrik/ngx-leaflet geojson interpretation.
My geojson is automatically generated with color and opacity corresponding to the map information. Each geojson is a FeatureCollection with multiple Polygons differently styled.
I've tried different options, I've found to set style of the Polygons, but non of them seems to work.
Example geoJson:
{
"type":"FeatureCollection",
"features":[
{
"geometry":{
"type":"Polygon",
"coordinates":[
[...]
]
},
"type":"Feature",
"properties":{
"style":{
"color":"#ff0000",
"weight":0,
"opacity":0.2
}
}
}, ... ]}
I've been trying multiple options that seems to work for people. Also there is nothing on styling inside geojson in the ngx-leaflet docs.
How should I set the styling above to make ngx-leaflet display proper color, stroke and opacity?
ngx-leaflet doesn't do anything to manipulate the layers you add to the map. So, it's up to you to provide the style options when you create the features.
E.g.,
let geoJson = {...};
let options = { "color": "#ff7800", "weight": 5, "opacity": 0.65 };
L.geoJSON(geoJson, options).addTo(map);
This example was taken from here: http://leafletjs.com/examples/geojson/

Show geojson featureCollection with Leaflet

with QGIS I´ve exported a polygon layer as geojson which I´d like to publish with leaflet. This is how the geojson looks like [exluded due to SO character limits]:
https://gist.github.com/t-book/88806d12d7f05024b147715be82e6844
This is what I´ve tried:
Wrapped geojson as var:
var states = [{
"type": "FeatureCollection",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:EPSG::31468" } },
"features": [
{ "type": "Feature", "properties": ...
}];
Added as new Layer:
L.geoJSON(states, {
style: function(feature) {
switch (feature.properties.party) {
case 'Euerbach': return {color: "#ff0000"};
case 'Werneck': return {color: "#0000ff"};
}
}
}).addTo(map);
Unfortunately nothing is rendered. How do I correctly add this geojson featureCollection to my map?
The problem is that your data is projected - Leaflet is expecting your data to be unprojected (composed of long/lat pairs, or "projected" in WGS84/EPSG 4326). There are a few solutions, two come to mind here:
In QGIS, export your data so that it is composed of long/lat coordinate pairs
Use proj4.js to reproject your coordinates when displaying the geojson.
For number two, you'll need to set the coordsToLatLng option when adding the geojson as a layer:
var geojson = L.geoJSON(states, {
coordsToLatLng: function (p) {
// return get lat/lng point here.
})
The body of this function will take a coordinate in the geojson's coordinate reference system (CRS) and return it in WGS84 using proj4.
Also, coordsToLatLng function expects you to return lat/long pairs. As your geojson and proj4 represent data that is [x,y], we need to swap our values before returning the new point.
This could look like:
var geojson = L.geoJSON(states, {
coordsToLatLng: function (p) {
p = proj4(fromProjection,toProjection,p); // reproject each point
p = [p[1],p[0]] // swap the values
return p; // return the lat/lng pair
}
}).addTo(map);
Of course, we need to define our CRSs. I looked up your CRS (it is specified in the geojson itself) on spatialreference.org and used the provided description for that CRS and EPSG4326 (WGS84) to set my fromProjection and toPojection:
var fromProjection = '+proj=tmerc +lat_0=0 +lon_0=12 +k=1 +x_0=4500000 +y_0=0 +ellps=bessel +datum=potsdam +units=m +no_defs ';
var toProjection = "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs ";
Altogether that gives us something like this.
Keep in mind, that if you have large files, reprojecting them in javascript will take longer than if you export them in the proper CRS.

Mapbox: Filtering out markers in a Leaflet Omnivore KML layer

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.

Updating layers in Leaflet / Mapbox

I'm trying to make a mapping visualization in realtime, where I keep getting new points via websockets. The initial plotting these markers on the map seems simple, but I'm not sure what's the right way of updating a layer on Mapbox.
As of now, whenever I get a new point, I remove the old layer, create a new one and then add it on the map. The problem with this approach is that it is slow and for high number of points (>5000) it starts lagging.
// remove layer
if (this.pointsLayer != null) {
map.removeLayer(this.pointsLayer);
}
// build geoJSON
var geoJSON = { "type": "FeatureCollection", "features": [] };
geoJSON["features"] = tweets.map(function(tweet) {
return this.getGeoPoint(tweet);
}.bind(this));
// add geoJSON to layer
this.pointsLayer = L.mapbox.featureLayer(geoJSON, {
pointToLayer: function(feature, latlon) {
return L.circleMarker(latlon, {
fillColor: '#AA5042',
fillOpacity: 0.7,
radius: 3,
stroke: false
});
}
}).addTo(map);
Is there a better way?
You can create an empty GeoJSON layer by passing it a false instead of real data:
//create empty layer
this.pointsLayer = L.mapbox.featureLayer(false, {
pointToLayer: function(feature, latlon) {
return L.circleMarker(latlon, {
fillColor: '#AA5042',
fillOpacity: 0.7,
radius: 3,
stroke: false
});
}
}).addTo(map);
then use .addData to update it as new tweets come in. Something like:
// build geoJSON
var geoJSON = { "type": "FeatureCollection", "features": [] };
geoJSON["features"] = /**whatever function you use to build a single tweet's geoJSON**/
// add geoJSON to layer
this.pointsLayer.addData(geoJSON);
For a single tweet, I guess you could just create a Feature instead of a FeatureCollection, though I don't know whether that extra layer of abstraction would make any difference in terms of performance.
EDIT: Here is an example fiddle showing the .addData method at work:
http://jsfiddle.net/nathansnider/4mwrwo0t/
It does slow down noticeably if you add 10,000 points, and for 15,000 points, it's really sluggish, but I suspect that has less to do with how the points are added that the demands of rendering so many circleMarkers.
If you aren't already, you may want to try using the new Leaflet 1.0 beta, which redraws vector layers faster and is generally much more responsive with large datasets. Compare this 15,000-point example using Leaflet 0.7.5 to the same code using Leaflet 1.0.0b2. Not everything is fixed (popups take a long time to open in both), but the difference in lag time when trying to drag the map is pretty dramatic.
There's no reason to go through the intermediate step of construction a GeoJSON object just so you can add it to the map. Depending on your exact needs, you can do something like this:
tweets.forEach(function(t) {
L.marker(this.getGeoPoint(t)).addTo(map);
}, this);
You should manage the tweets object so it only contains points that are not already visible on the map, though. Deleting all the old markers, just so you can add them again, is of course going to be very slow.
I would take a look at Leaflet Realtime:
Put realtime data on a Leaflet map: live tracking GPS units, sensor data or just about anything.
https://github.com/perliedman/leaflet-realtime

Missing info of feature when getting it from Leaflet's event.layer.toGeoJSON()

I'm using L.geoJson and adding layer to my map, then with items.on('click', function (event) {}) displaying info of selected object which is stored in event.layer (getting info with toGeoJSON()).
Problem is, when there are some of the items, everything seems to work, but now when there are >1000 polygons, some of the data when using on('click') does not contain my info of the feature inside event.layer.
What could be a problem?
ADDITIONAL INFO:
Our GeoJSON looks something like this, it has additional data like ID and various properties.
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"id": 1,
"geometry": {"type": "Point", "coordinates": [102.0, 0.5]},
"properties": {"prop1": "value1"}
},
{
"type": "Feature",
"id": 2,
"geometry": {"type": "Point", "coordinates": [142.0, 15.5]},
"properties": {"prop1": "value2"}
}
]
}
I put everything on a map:
data = L.geoJson(data);
allItems.clearLayers().addLayer(data);
Features are displayed on a map.
Then I listen for clicks on the features on the map:
allItems.on('click', function (event) {
// On many of the features this is empty,
// on some data can be retrieved.
// On some that doesn't have ID, properties
// are empty too
console.log(event.layer.toGeoJSON().id);
});
GeoJSON has been checked and ID and properties ARE THERE.
Here's a little explanation about how to handle click events on L.GeoJSON layer and/or it's features. I've commented the code to explain what is going on and added an example on Plunker for you so you can test the concept.
Example on Plunker: http://plnkr.co/edit/TcyDGH?p=preview
Code:
var geoJsonLayer = L.geoJson(data, {
// Executes on each feature in the dataset
onEachFeature: function (featureData, featureLayer) {
// featureData contains the actual feature object
// featureLayer contains the indivual layer instance
featureLayer.on('click', function () {
// Fires on click of single feature
console.log('Clicked feature layer ID: ' + featureData.id);
});
}
}).on('click', function () {
// Fires on each feature in the layer
console.log('Clicked GeoJSON layer');
});
As for your code, i'm quite confused as to where the allItems comes from. A layerGroup or something like that? Trying to capture clicks on individual features in the GeoJSON layer on that object won't work, because it won't be able to differentiate between the features. Same goes for the handler on the GeoJSON layer, it will fire, but won't know which feature you clicked. The handler in the onEachFeature function will. But i'm assuming you understand by now if your understanding the code/example above.