Is it possible to export geojson from leaflet to save the map state?
I want to store the markers, zoom & map center to load it later.
There is plenty of ways to load geojson on leaflet, but I can't figure out any option to export the map to geojson...
There's no "out-of-the-box" option to export all the markers on the map to GeoJSON but it's something you can do easily do yourself. Leaflet's L.Marker has a toGeoJSON method:
Returns a GeoJSON representation of the marker (GeoJSON Point Feature).
http://leafletjs.com/reference.html#marker-togeojson
For example:
// Create a marker
var marker = new L.Marker([0, 0]);
// Get the GeoJSON object
var geojson = marker.toGeoJSON();
// Log to console
console.log(geojson);
Will output to your console:
{
"type":"Feature",
"properties":{},
"geometry":{
"type":"Point",
"coordinates":[0,0]
}
}
If you want to store all the markers added to your map in a GeoJSON collection, you could do something like this:
// Adding some markers to the map
var markerA = new L.Marker([0, 0]).addTo(map),
markerB = new L.Marker([1, 1]).addTo(map),
markerC = new L.Marker([2, 2]).addTo(map),
markerD = new L.Marker([3, 3]).addTo(map);
// Create an empty GeoJSON collection
var collection = {
"type": "FeatureCollection",
"features": []
};
// Iterate the layers of the map
map.eachLayer(function (layer) {
// Check if layer is a marker
if (layer instanceof L.Marker) {
// Create GeoJSON object from marker
var geojson = layer.toGeoJSON();
// Push GeoJSON object to collection
collection.features.push(geojson);
}
});
// Log GeoJSON collection to console
console.log(collection);
Will output to your console:
{
"type":"FeatureCollection",
"features":[{
"type":"Feature",
"properties":{},
"geometry":{
"type":"Point",
"coordinates":[0,0]
}
},{
"type":"Feature",
"properties":{},
"geometry":{
"type":"Point",
"coordinates":[1,1]
}
},{
"type":"Feature",
"properties":{},
"geometry":{
"type":"Point",
"coordinates":[2,2]
}
},{
"type":"Feature",
"properties":{},
"geometry":{
"type":"Point",
"coordinates":[3,3]
}
}]
}
Edit: However, as the QP found out, if you're able to put your markers into a L.LayerGroup, L.FeatureGroup or L.GeoJSON layer you can just use it's toGeoJSON method which returns a GeoJSON featurecollection:
Returns a GeoJSON representation of the layer group (GeoJSON FeatureCollection).
http://leafletjs.com/reference.html#layergroup-togeojson
If you want to store the map's current bounds (center and zoom) you could simply add it to the collection doing this:
var bounds = map.getBounds();
var collection = {
"type": "FeatureCollection",
"bbox": [[
bounds.getSouthWest().lng,
bounds.getSouthWest().lat
], [
bounds.getNorthEast().lng,
bounds.getNorthEast().lat
]],
"features": []
};
You can later restore it by using the bbox member in conjunction with L.Map's setBounds method. That's it. You could send it to the server or download it via dataurl whatever you like. Hope that helps, good luck.
I've found a simplier solution based on iH8's answer and a colleague's help.
First, create a FeatureGroup layer and add it to the map:
var drawnItems = new L.FeatureGroup();
map.addLayer(drawnItems);
Then add the marker (or other elements) to the layer:
var marker = new L.marker([lat, lon]).addTo(drawnItems);
And export everything when you want to :
var collection = drawnItems.toGeoJSON();
var bounds = map.getBounds();
collection.bbox = [[
bounds.getSouthWest().lng,
bounds.getSouthWest().lat,
bounds.getNorthEast().lng,
bounds.getNorthEast().lat
]];
// Do what you want with this:
console.log(collection);
Related
GIS data and python are old hat to me but I am very new to web development and geospatial web applications.
I have followed a tutorial and a class that I am taking to get to the below script but I cannot get the resulting geojson object (the polygon layer) to display within leaflet. I can however, log all of the features of the polygon layer to the console. Furthermore, within the console I can clearly see the correct type, properties, and coordinate arrays of the geojson object. I can also clearly see all of the features within the leaflet map object within the console.
Any input would be greatly appreciated. If needed I will be happy to post the getData.php code. I just don't think that is the problem.
var map,
fieldsin = ["campus_nam", "status", "schnumber", "type"],
autocomplete = [];
$(document).ready(initialize);
function initialize(){
map = L.map("mapdiv", {
center: [36.10, -80.25],
zoom: 12
});
var backgroundLayer = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png').addTo(map);
//adding postgresql layers to map with getData.php
getData(fieldsin);
};
function getData(fieldsin){
$.ajax({
url: "php/getData.php",
data: { table: "public.school_campus", fields: fieldsin },
success: function(data){
mapData(data);
}
})
};
function mapData(data){
//remove existing map layers
map.eachLayer(function(layer){
//if not the tile layer
if (typeof layer._url === "undefined"){
map.removeLayer(layer);
}
})
//create geojson container object
var geojson = {
"type": "FeatureCollection",
"features": []
};
//split data into features
var dataArray = data.split(", ;");
//pop off the last value of the array because it is an empty string.
dataArray.pop();
//build geojson features
dataArray.forEach(function(d){
d = d.split(", "); //split the comma seperated data string up into individual attribute values
var test = d[fieldsin.length].concat("}");
//feature object container
var feature = {
"type": "Feature",
"properties": {}, //properties object container
//"geometry": JSON.parse(d[fieldsin.length]) //parse geometry
"geometry": JSON.parse(d[fieldsin.length]) //parse geometry
};
//bulding properties for properties container above
for (var i=0; i<fieldsin.length; i++){
feature.properties[fieldsin[i]] = d[i];
};
//add feature names to autocomplete list
if ($.inArray(feature.properties.campus_nam, autocomplete) == -1){
autocomplete.push(feature.properties.campus_nam);
};
//console.log(feature.geometry)
geojson.features.push(feature);
//var campusLayer = L.geoJSON(geojson).addTo(map);
var campusLayer = L.geoJSON(geojson, {
style: {
fillColor: "#CC9900",
color: "#66ffff",
weight: 1
},
onEachFeature: function (feature, layer) {
var html = "";
for (prop in feature.properties){
html += prop+": "+feature.properties[prop]+"<br>";
};
layer.bindPopup(html);
}
}).addTo(map);
});
};
Adding a sample of your resulting GeoJSON object would have surely helped in understanding your situation.
However I highly suspect that you have simply inverted the coordinates:
Leaflet expects [latitude, longitude] order
GeoJSON expects [longitude, latitude] order
See also https://macwright.org/lonlat/
Therefore there is a very high chance your Leaflet GeoJSON Layer Group is actually added onto your map, but you would have to zoom out to see your features on a completely different place and distorded.
Looks like you also have not specified the appropriate CRS to your Leaflet map instance, or you need to convert the coordinates from your backend to the Leaflet's default EPSG:3857.
Note that the GeoJSON spec requests WGS84 CRS, which is the same input for EPSG:3857.
I have a bunch of polygons which are stored in a database. I would like to add them to the map in such a way that they can be edited using the leaflet-draw toolbar. Although, now the polygons get added to the map, I am unable edit them.
I think this is because they are not added to the layerGroup() to which newly drawn shapes are added.
Please help.
You have to add your polygons to the featureGroup drawnItems ! Let's say,
var polyLayers = dbArray;
is your database array with polygons. First create a feature group with your drawn items:
var drawnItems = new L.FeatureGroup();
and add it to the map:
map.addLayer(drawnItems);
Then you simply need to iterate over your polygons from your database and add them to the drawnItems FeatureGroup:
for(layer of polyLayers) {
drawnItems.addLayer(layer);
};
Now the layers are added to the map and editable.
Here goes an EXAMPLE:
var drawnItems = new L.FeatureGroup();
map.addLayer(drawnItems);
var polyLayers = [];
var polygon1 = L.polygon([
[51.509, -0.08],
[51.503, -0.06],
[51.51, -0.047]
]);
polyLayers.push(polygon1)
var polygon2 = L.polygon([
[51.512642, -0.099993],
[51.520387, -0.087633],
[51.509116, -0.082483]
]);
polyLayers.push(polygon2)
// Add the layers to the drawnItems feature group
for(let layer of polyLayers) {
drawnItems.addLayer(layer);
}
Salutations all and happy holidays.
I Noticed an interesting behavioral quirk while trying to draw polygon layers with L.geoJson(). consider the following code:
var polygonCoords = [
{"type": "Feature",
"properties": {"group": "Violations"},
"geometry": {
"type" : "Polygon",
"coordinates": [[
[-107.69348, 43.22519],
[-105.48523, 42.99259],
[-107.7594, 42.26105]
]]
}
}];
and
var polygons = L.polygon([
[43.22519, -107.69348],
[42.99259, -105.48523],
[42.26105, -107.7594]
]);
Now, both work in their respective contexts. I was just wondering why the coordinate matrix within L.polygon() has to be reflected in order to show up where one expects it to be when passed into L.goeJson() like so:
var jsonPoly = L.geoJson(polygonCoords, {
style: function(feature) {
if (feature.properties.group == "Violations") {
return {color: "#ff0000"};
}
}
});
Or is this an oversight within leaflet? Also, is there a way to automate this reflection with say toGeoJson(polygons)?
Thanks so much all.
When creating a geoJson layer the coordinates are expected to match the GeoJSON standard (x,y,z or lng, lat, altitude) (GeoJSON position specs)
If you have string of GeoJSON where your coordinates are not in this format, you can create your GeoJSON layer with a custom coordsToLatLng function that will handle this conversion to the standard's format (Leaflet Doc)
If you have a polygon layer and want to add it to an existing GeoJSON feature group you can do something like:
var polygons = L.polygon([
[43.22519, -107.69348],
[42.99259, -105.48523],
[42.26105, -107.7594]
]);
var gg = polygons.toGeoJSON();
var jsonFeatureGroup = L.geoJson().addTo(map);
jsonFeatureGroup.addData(gg);
map.fitBounds(jsonFeatureGroup.getBounds());
I have a basic markerclusterer example which works very well.
var center = new google.maps.LatLng(37.4419, -122.1419);
var options = {
'zoom': 13,
'center': center,
'mapTypeId': google.maps.MapTypeId.ROADMAP
};
var map = new google.maps.Map(document.getElementById("map"), options);
var markers = [];
for (var i = 0; i < 100; i++) {
var latLng = new google.maps.LatLng(data.photos[i].latitude,
data.photos[i].longitude);
var marker = new google.maps.Marker({'position': latLng});
markers.push(marker);
}
var markerCluster = new MarkerClusterer(map, markers);
What I would like to do is cluster the markers by country and then once you click on it they are still clustered until on3 further click. Currently they are clustered until you are down to one result. I have thousands of markers and would like them visible after one country click and then one more click.
I looked for a solution online and found this http://google-maps-utility-library-v3.googlecode.com/svn/tags/markermanager/1.0/examples/google_northamerica_offices.html
which is produced using this
var officeLayer = [
{
"zoom": [0, 3],
"places": [
{ "name": "US Offices", "icon": ["us", "flag-shadow"], "posn": [40, -97] },
{ "name": "Canadian Offices", "icon": ["ca", "flag-shadow"], "posn": [58, -101] }
]
},
...
};
function setupOfficeMarkers() {
allmarkers.length = 0;
for (var i in officeLayer) {
if (officeLayer.hasOwnProperty(i)) {
var layer = officeLayer[i];
var markers = [];
for (var j in layer["places"]) {
if (layer["places"].hasOwnProperty(j)) {
var place = layer["places"][j];
var icon = getIcon(place["icon"]);
var title = place["name"];
var posn = new google.maps.LatLng(place["posn"][0], place["posn"][1]);
var marker = createMarker(posn, title, getIcon(place["icon"]));
markers.push(marker);
allmarkers.push(marker);
}
}
mgr.addMarkers(markers, layer["zoom"][0], layer["zoom"][1]);
}
}
mgr.refresh();
updateStatus(mgr.getMarkerCount(map.getZoom()));
}
I'm not sure how to implement this into what I've currently got and if i need to include any other scripts/ libraries also.
You are looking at two totally different libraries, there. Your question is about the MarkerClusterer library, but your example solution is about the MarkerManager library.
The MarkerClusterer library automatically clumps markers together based on an algorithm that tries to decide when too markers would be so close together that you can't visibly distinguish one from another. You don't really have a lot of control over when and how it decides to merge markers together this way, so this library is idea when it doesn't matter to you how they get merged, as long as merging happens. Since you want to merge markers together by political boundaries (countries) and not by proximity to each other, this is not the library for you.
The MarkerManager library does not automatically merge markers together at all. What it does do is to selectively hide and reveal markers based on the zoom level of the current map viewport. What you would need to do is do your own merging, and then add to the MarkerManager all of the merged markers, as well as the detail markers, and the zoom levels where you want each marker to be visible. Doing your own merging means you will need an alternate way of determining which country each marker point falls within. Hopefully, you already know (or can get) that information, because it's not automatically provided by any of these libraries.
tl;dr - use the MarkerManager library and not the MarkerClusterer library for grouping by countries, and it's up to you to identify the location for each country and which marker goes with which one.
My code to make markers:
for (var marker in markers) {
var posMarker = new google.maps.Marker({
position: new google.maps.LatLng(markers[marker].lat, markers[marker].lng),
map: map,
visible: markers[marker].visible
});
};
My markers object:
var markers = {
"London": {"lat": -83.68088192646843, "lng": -125.270751953125, "type": "town", "visible": false},
"Paris": {"lat": -58.1548020417031, "lng": -21.318115234375, "type": "town", "visible": false},
};
I'm trying to be able to toggle the markers with a checkbox like so:
$('#toggle').change(function() {
for (var marker in markers) {
posMarker.setVisible(true);
};
});
But only the last marker in the array is shown, how do I make all of them appear?
Thanks.
Well, I see posMarker being used as a temporary variable that places a Google Maps marker, and as the for loop progresses, the posMarker reference "updates" to the latest marker placed. That's why only the last marker is being shown.
You need to keep track of all references to Google Maps markers being placed, including those that have been "consumed". My approach uses an object, much like your markers object but holding references to Google Maps markers. You could also use a plain indexed array (posMarkers[]). It's up to you.
See the Demo, note the LatLngs have been modified for simplicity (looks like you have a custom coordinate system).
Also, I didn't make this change, but I just noticed that it may make more sense to call marker in markers, city in markers because the way your object is written. It would be more readable, but won't affect the execution.
Finally, semicolons at the end of for loops blocks are unneeded, and be careful with the trailing comma after the Paris object (I'm guessing you just erased the rest of the list). In this case it didn't matter, but other times these trailing commas can be a source of hard-to-find bugs.
function initialize() {
map = new google.maps.Map(document.getElementById("map_canvas"), mapOptions);
var markers = {
"London": { "lat": 0, "lng": 0, "type": "town", "visible": false },
"Paris": { "lat": 10, "lng": 10, "type": "town", "visible": false }
};
var posMarkers = {};
for (var marker in markers) {
posMarkers[marker] = new google.maps.Marker({
position: new google.maps.LatLng(markers[marker].lat, markers[marker].lng),
map: map,
visible: markers[marker].visible
});
}
$('#toggle').change(function () {
for (var marker in markers) {
if (posMarkers[marker].getVisible()) {
posMarkers[marker].setVisible(false);
}
else {
posMarkers[marker].setVisible(true);
}
}
});
}