I am attempting what I imagine to be a fairly common use-case with a leaflet multipolygon object.
I create the MultiPolygon using geojson:
var layer = L.GeoJSON(g, style_opts);
What I'd like is to put a simple text label in the center of each polygon. (For example, something like putting state name in the center of each state).
I've looked at:
https://groups.google.com/forum/?fromgroups=#!topic/leaflet-js/sA2HnU5W9Fw
Which actually overlays the text, but when I add a bunch of polygons, it appears to put the label off-center in weird ways, and I'm currently unable to track down the problem.
I've also looked at: https://github.com/jacobtoye/Leaflet.label
but that appears to only put the label on polygons when you mouse over the polygon, and does not stay statically on the polygon.
I think my best course of action is to use that first link, and track down why it's changing the location, but in the meantime, if anyone knows of a quick and easy way to lay a label on a polygon in leaflet, I'd be much obliged.
Also, if I have any faulty assumptions about the two links above, please feel free to straighten me out.
Thanks very much in advance.
The leaflet label plugin also allows static labels, see the demo.
The only reason the polyline label is not static is that it moves around as you move along the polyline.
You can probably do better than this, by overriding bindLabel() for Polygons but this is a simple way to add a static label to the center of a polygon:
label = new L.Label()
label.setContent("static label")
label.setLatLng(polygon.getBounds().getCenter())
map.showLabel(label);
http://jsfiddle.net/CrqkR/6/
You can use the onEachFeature option of L.geoJson to create a new L.divIcon for each polygon.
L.geoJson(geoJsonData, {
onEachFeature: function(feature, layer) {
var label = L.marker(layer.getBounds().getCenter(), {
icon: L.divIcon({
className: 'label',
html: feature.properties.NAME,
iconSize: [100, 40]
})
}).addTo(map);
}
);
you can use this code to show label in polygon :
var eu = L.geoJSON(euCountries, {
onEachFeature: function (feature, layer) {
// if (feature.geometry.type === "Polygon") {
var bounds = layer.getBounds();
// Get center of bounds
var center = bounds.getCenter();
//var center = layer.getBounds().getCenter();
if(feature.properties.name=="russia")
{
alert(center)
}
layer.bindTooltip(feature.properties.name, {permanent: true, direction: "center", className: "my-labels"});
layer.on("click", function (e) {
layer.bindPopup(feature.properties.name);
});
/* var marker =L.circleMarker(center, {color: '', radius:10,Title:20}).bindTooltip(feature.properties.name, {permanent: true, direction: "center", className: "my-labels"});
map.addLayer(marker);*/
// var polygonAndItsCenter = L.layerGroup([layer, marker]);
// }
},
});
eu.addTo(map);
Related
I want to add a marker in the middle of a polygon that is made form geojson data. The polygon is connected a control where the layer can be turned on and off. This marker should only be displayed when the layer is active. I have the following code:
var geoJsonLayer = L.geoJSON(Locations, {
onEachFeature: function (feature, layer) {
if (feature.geometry.type === "Polygon") {
var bounds = layer.getBounds();
var center = bounds.getCenter();
var markerTitle = feature.properties.ItemId;
layer.id = markerTitle;
var popUpFormat = dataPopUp(feature);
layer.bindPopup(popUpFormat, customPopUpOptions);
}
},
});
Thanks for your interest and I hope someone can help me :D
You want to group a L.Polygon and a L.Marker together, and treat them as the same entity. This is a textbook scenario for using L.LayerGroups, e.g.
var geoJsonLayer = L.geoJSON(Locations, {
onEachFeature: function (feature, layer) {
if (feature.geometry.type === "Polygon") {
var center = layer.getBounds().getCenter();
var marker = L.marker(center);
var polygonAndItsCenter = L.layerGroup([layer, marker]);
}
},
});
Now polygonAndItsCenter is a L.LayerGroup with the polygon and its center (so adding/removing to/from the map will apply to both), but geoJsonLayer will contain only the polygons. How you handle that is up to you, but I guess you might want to not add geoJson to the map (using only for parsing and instantiating the polygons), and keep track of your polygon+marker LayerGroups separately, e.g.
var polygonsWithCenters = L.layerGroup();
var geoJsonLayer = L.geoJSON(Locations, {
onEachFeature: function (feature, layer) {
if (feature.geometry.type === "Polygon") {
var center = layer.getBounds().getCenter();
var marker = L.marker(center);
var polygonAndItsCenter = L.layerGroup([layer, marker]);
polygonAndItsCenter.addTo(polygonsWithCenters);
}
},
});
// geoJsonLayer.addTo(map); // No!!
polygonsWithCenters.addTo(map);
// Do something with a polygon+marker, e.g. remove the first one from the map
polygonsWithCenters.getLayers()[0].remove();
There are a few secondary problems that can spawn for this, so think about what you want to do with each polygon/layergroup/marker before writing code, keep the Leaflet documentation at hand, and remember:
You can not attach events or bind popups to LayerGroups, but you can do that to L.FeatureGroups
The center of a polygon's bounding box is different from its centroid which is different from the point inside the polygon which is furthest away from any of its edges. Only the third option is guaranteed to be inside the polygon.
I made a leaflet map with different layers which look like this:
L.easyButton('<span class ="buttons">Example </span>', function (btn, map) {
if (map.hasLayer(example1)) {
map.removeLayer(example1);
};
if (map.hasLayer(example2)) {
map.removeLayer(example2);
};
if (map.hasLayer(example3)) {
map.removeLayer(drittpa2017zweit);
}
map.addLayer(example4);
}, 'FirstExample').addTo(map);
and so on...
I declared a legend, which I only want to use when one layer is on, right now it appears all the time I think because of this
legendwahlbeteiligung.addTo(map);
I tried everything I saw on here to make it visible only on one layer but then they all disappear...
I have the same problem with a marker I use, which I also only want to show on one of the mentioned layer...
Can anyone tell me how I can make the legend and marker only appear when one layer is clicked on?
To make a marker myMarker appear when the layer example1 is visible and disappear when it's not:
var myMarker = L.marker(...);
example1.on('add', function(e) {
if (! map.hasLayer(myMarker)) {
myMarker.addTo(map);
}
});
example1.on('remove', function(e) {
if (map.hasLayer(myMarker)) {
myMarker.removeFrom(map);
}
});
So my idea seems pretty straight forward to me but I struggle nevertheless. What I want to do is basically click on any point of my map and draw a polygon on the main feature, i.e. if I click on a park or a building that specific polygon is displayed and highlighted.
I used a lot of this code: https://www.mapbox.com/mapbox-gl-js/example/queryrenderedfeatures-around-point/
But instead of giving it a set of geojson I want my javascript to select to needed geojson data on mousover (eventhough i am not sure whether that works in general). Right now my code snipped compiles but doesn't show anything.
In a later step I want to collect all polygons of the same feature, i.e. all parks, and display them as highlighted polygons and then export them as a svg file which only consists of the map representations of the feature clicked on. Maybe someone has an idea for that as well?
Thanks in regard :)
This is my javascript as of now:
//Set AccessToken from MapBox
mapboxgl.accessToken = 'pk.eyJ1IjoidG1pbGRuZXIiLCJhIjoiY2o1NmlmNWVnMG5rNzMzcjB5bnV3YTlnbiJ9.r0BCga0qhRaHh0CnDdcGBQ';
//Setup starting view point at Uni-Bremen campus
var map = new mapboxgl.Map({
container: 'content-map',
style: 'mapbox://styles/mapbox/streets-v9',
center: [8.85307, 53.10810],
zoom: 16
});
//Add a search bar -> hidden for presentation
/*map.addControl(new MapboxGeocoder({
accessToken: mapboxgl.accessToken
}));*/
//Function to show all Features of a certian point
map.on('mousemove', function (e) {
var features = map.queryRenderedFeatures(e.point);
document.getElementById('features').innerHTML = JSON.stringify(features, null, 2);
console.log(JSON.stringify(features, null, 2));
drawPolygon();
});
//Draw a Polygon
function drawPolygon () {
//set boundary box as 5px rectangle area around clicked point
var bbox = [[e.point.x - 5, e.point.y - 5], [e.pont.x + 5, e.point.y + 5]];
//set the data on pointer using the bbox
var data = map.queryRenderedFeatures(bbox);
map.on('load', function() {
var dataSource = 'school';
//set school to the feature and use 'setJsonData' as data source.
map.addSource(dataSource, {
'type': 'geojson',
'data': data
});
//adding a new layer for the general display
map.addLayer({
'id': 'dataSet',
'type': 'fill',
'source': dataSource,
'source-layer': 'original',
'paint': {
'fill-outline-color': 'rgba(0,0,0,0.1)',
'fill-color': 'rgba(0,0,0,0.1)'
}
}, 'place-city-sm' ); //place polygon under these labels
//adding a new layer for the polygon to be drawn
map.addLeyer({
'id': 'dataSet-highlighted',
'type': 'fill',
'source': dataSource,
'source-layer': 'original',
'paint': {
'fill-outline-color': '#484896',
'fill-color': '#6e599f',
'fill-opacity': 0.75
},
'filter': ['in', 'FIPS', '']
}, 'place-city-sm'); //place polygon under these labels
//action on click to show the polygon and change their color
map.on('click', function (e) {
//retrieve data from 'dataSource'
var dataFromSource = map.queryRenderedFeatures(bbox, {layers: ['dataSource'] });
// Run through the selected features and set a filter
// to match features with unique FIPS codes to activate
// the `counties-highlighted` layer.
var filter = dataSource.reduce(function(memo, dataSource) {
memo.push(dataSource, properties.FIPS);
return memo;
} ['in', 'FIPS'] );
map.setFilter('dataSet-highlighted', filter);
});
});
}
I'm not 100% sure what you're asking, but my interpretation is that you'd like to specifically style certain types of geometry when you hover over them, such as "parks". You're on the right path, where using map.queryRenderedFeatures() is great. I've put together an example using the same Mapbox Streets style that queries only the building layer and looks for type university on mouseover.
When the interaction encounters a proper feature, it updates the source data with the new feature, which then updates the school-hover layer.
Check out the pen here: https://codepen.io/mapsam/pen/oemqKb
In a later step I want to collect all polygons of the same feature, i.e. all parks, and display them as highlighted polygons and then export them as a svg file which only consists of the map representations of the feature clicked on.
I won't go into file exports, but just remember that all results returned from map.queryRenderedFeatures are specific to the single vector tile you are querying, which can lead to issues on tile boundaries where a polygon isn't fully covered by your current query.
Check out this example where we are highlighting features with similar data, which should allow you get all of the necessary geometries and export to SVG.
Cheers!
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
I use the following block of JavaScript to try to show a WMS layer. I'm using OpenLayers 2.8.
The map's base layer (Openstreetmap) shows correctly, it zooms to the correct area, the "pyramid" layer is shown in the layer switcher, but no request to its WMS service is ever made (so the fact that the URL, styles and params are dummies shouldn't matter -- it never even attempts to get them).
OpenLayers does try to get a WMS layer once I pan or zoom far enough so that the Gulf of Guinea is in view (but all my data is in the Netherlands). This suggests a projection problem (WGS84's (0, 0) point is there), but I don't understand why OpenLayers doesn't even try to fetch a map layer elsewhere. My data is in EPSG:3857 (Web Mercator) projection.
/*global $, OpenLayers */
(function () {
"use strict";
$(function () {
$(".map").each(function () {
var div = $(this);
var data_bounds = div.attr("data-bounds");
console.log("data_bounds: " + data_bounds);
if (data_bounds !== "") {
var map = new OpenLayers.Map(div.attr("id"), {
projection: "EPSG:3857"});
var extent = JSON.parse(data_bounds);
var bounds = new OpenLayers.Bounds(
extent.minx, extent.miny,
extent.maxx, extent.maxy);
map.addLayer(
new OpenLayers.Layer.OSM(
"OpenStreetMap NL",
"http://tile.openstreetmap.nl/tiles/${z}/${x}/${y}.png",
{buffer: 0}));
map.addLayer(
new OpenLayers.Layer.WMS(
"pyramid", "http://rasterserver.local:5000/wms", {
layers: "test",
styles: "test"
}, {
singleTile: true,
isBaseLayer: false,
displayInLayerSwitcher: true,
units: 'm'
}));
map.addControl(new OpenLayers.Control.LayerSwitcher());
map.zoomToExtent(bounds);
}
});
});
})();
Edit: the 'data_bounds' console print prints (with some added formatting):
data_bounds: {
"minx": 582918.5701295201,
"miny": 6923595.841021758,
"maxx": 821926.9006116659,
"maxy": 7079960.166533174
}
It zooms to the correct region in the north of the Netherlands, so I don't think the problem is there.
Since posting, I found out that if I don't use the OSM layer, and instead use the WMS layer as baselayer, it works. So perhaps there's some incompatibility with a OSM baselayer and a WMS layer added to it? But then I don't get that it does seem to do something near WGS84 (0, 0).
I eventually managed to fix this by giving the map an explicit maxExtent:
var extent = JSON.parse(data_bounds);
var bounds = new OpenLayers.Bounds(
extent.minx, extent.miny,
extent.maxx, extent.maxy);
var map = new OpenLayers.Map(div.attr("id"), {
projection: "EPSG:3857",
maxExtent: bounds
});
Oddly enough this doesn't limit the user's ability to pan and zoom around the world, but it does make the overlay work...