I have a map that changes tiles based on four radio buttons. I need the popup window that appears when you roll over a tile to change as the different map layers change. I've gotten it to appear but when I switch layers the map just adds another popup window. I tried using control.removeFrom(map) but it doesn't seem to work. I think my logic may be screwed up somewhere. Here is one of the if statements:
if (two == true && black == true) {
function blkNineStyle(feature) {
return {
fillColor: getColor(feature.properties.pctBlack9000),
weight: 2,
opacity: 1,
color: '#666',
dashArray: '2',
fillOpacity: 0.9
};
}
//Tried to us this to take off the control.
info.removeFrom(map);
map.removeLayer(geojson);
geojson = L.geoJson(tracts, {style: blkNineStyle, onEachFeature: onEachFeature}).addTo(map);
var info = L.control();
info.onAdd = function (map) {
this._div = L.DomUtil.create('div', 'info');
this.update();
return this._div;
};
info.update = function (props) {
this._div.innerHTML = '<h4>Percent White population change</h4>' + (props ? '<b>' + props.name + '</b><br />' + props.pctBlack9000 + '%' : 'Hover over a tract');
};
info.addTo(map);
}
You can see the (broken) map here.
I had this same problem myself and I just solved it.
I had to define an empty variable in the global environment (outside any functions you're using). This isn't a full script or anything, but the general idea I'm describing is below:
var info; // CREATING INFO VARIABLE IN GLOBAL ENVIRONMENT
function makeMap() {
..... geojsons, styles, other stuff ....
// REMOVING PREVIOUS INFO BOX
if (info != undefined) {
info.removeFrom(map)
}
// making current layer's info box
info = L.control();
info.onAdd = function (map) {
this._div = L.DomUtil.create('div', 'info');
this.update();
return this._div;
};
info.update = function (props) {
this._div.innerHTML = '<h4>Data by Zip Code</h4>' + (props ?
'<b>Zip Code: ' + props.id + '</b><br />Value: ' + matchKey(props.id, meanById)
: 'Hover over a zip code');
};
info.addTo(map);
..... other stuff again ......
} // end function
I am very new to both Leaflet and javascript, so I have to say that I'm not exactly sure where to place the info.removeFrom(map) line in the code you have posted at the map link you provided, but you are on the right track with 'info.removeFrom(map)' .
I was able to problem-solve my issue with dynamic legends and info boxes by fiddling around here: http://jsfiddle.net/opensas/TnX96/
I believe you want to remove the control similarly how you added it.
In this case leaflet provides direct remove() method similar to addTo(map) method.
Example-
Whenever you want to remove the legend control use following code-
Create Control-
var legendControl = L.control({position: 'bottomleft'});
legendControl.addTo(mymap);
Remove Control-
legendControl.remove();
For more details refer/click here...
Despite the fact that this question was asked a year ago, I recently had to come up with a solution to a similar problem myself so feel as if I should share in case anybody else ends up here like I did.
The L.control() object in Leaflet isn't technically a layer, and this is why trying to add and remove it some times doesn't work in the same way as for layers.
http://leafletjs.com/reference.html#icontrol
As the L.control constructor requires you only to "create all the neccessary DOM elements for the control", the HTML content of the div itself can be updated and deleted as and when required. Thus, to make a control feature appear and disappear from the map, and instead of adding and removing the L.control object, just adjust the HTML contents of the div contained by it. An empty div would result in no control feature being shown by the map.
Thus the above snippet would become:
//construct control, initialize div
info = L.control();
info.onAdd = function (map) {
this._div = L.DomUtil.create('div', 'info');
this.update();
return this._div;
};
if (two == true && black == true) {
function blkNineStyle(feature) {
return {
fillColor: getColor(feature.properties.pctBlack9000),
weight: 2,
opacity: 1,
color: '#666',
dashArray: '2',
fillOpacity: 0.9
};
}
//set div content to empty string; makes control disappear from map
info.getContainer().innerHTML='';
map.removeLayer(geojson);
geojson = L.geoJson(tracts, {style: blkNineStyle, onEachFeature: onEachFeature}).addTo(map);
//update content of control to make the control reappear
info.update = function (props) {
this._div.innerHTML = '<h4>Percent White population change</h4>' + (props ? '<b>' + props.name + '</b><br />' + props.pctBlack9000 + '%' : 'Hover over a tract');
};
}
//other cases...
if (two == false && black == true) {
//delete and update control where necessary
info.getContainer().innerHTML='';
Related
quick (and I believe for some of you an easy) question regarding cursor styling while hovering above geojson layer/s.
So, I have one clip layer that I'm using to create a mask around wms layers, and another one that represents some administrative areas.
As you can see in picture below
What I would like is to change style of cursor when I'm hovering above administrative areas but it seems that I'm missing something.
I'm trying to isolate to layer only administrative borders layer using this block of code:
map.on('pointermove', function(e) {
if (e.dragging) return;
var pixel = e.map.getEventPixel(e.originalEvent);
var hit = e.map.forEachFeatureAtPixel(pixel, function(feature, layer) {
return vectorJLS.get('layer_name') === 'jls';
});
e.map.getTargetElement().style.cursor = hit ? 'pointer' : '';
});
UPDATE
While JGH tweak code a bit it still doesn't work. I've detected that problem lies in layer that I'm using for mask clipping, when removed, code that JGH provided, works.
Here is code that I'm using for mask clipping
var clipLayer = new ol.layer.Image({
source: new ol.source.ImageVector({
source: new ol.source.Vector({
url: 'geojson/clip_wgs.geojson',
format: new ol.format.GeoJSON()
}),
style: new ol.style.Style({
fill: new ol.style.Fill({
color: 'black'
})
})
})
});
clipLayer.on('postcompose', function(e) {
e.context.globalCompositeOperation = 'source-over';
});
clipLayer.on('precompose', function(e) {
e.context.globalCompositeOperation = 'destination-in';
});
clipLayer.setMap(map);
Is it possible to somehow ignore clip layer when changing cursor style or should I take another approach?
UPDATE - 2
I've tweaked code a bit, but still without any success while clipedLayer is on.
map.on('pointermove', function(e) {
if (e.dragging) return;
var pixel = e.map.getEventPixel(e.originalEvent);
// initialize the hit variable to false (not found)
var hit = map.hasFeatureAtPixel(e.pixel, {
layerFilter: function(layer) {
return vectorJLS.get('layer_name') === 'jls';
}
});
console.log(hit)
});
Interesting problem if I might add
Finally, with help from fellow JGH I've found appropriate solution for my problem.
Searching release pages and google machine I've stumbled upon some interesting information regarding layer filters and its usage in method hasFeatureAtPixel. This block of code is valid for versions below 3.20.1 but more about that on OpenLayers Git
map.on('pointermove', function(e) {
if (e.dragging) return;
var pixel = e.map.getEventPixel(e.originalEvent);
var hit = map.hasFeatureAtPixel(e.pixel, {
layerFilter: function(layer) {
return layer.get('layer_name') === 'jls';
}
});
e.map.getTargetElement().style.cursor = hit ? 'pointer' : '';
});
For newer versions you should use layer filter like this (I'm using 4.6.5)
map.hasFeatureAtPixel(pixel, {
layerFilter: layerFilterFn.bind(layerFilterThis)
});
Or for my particular problem like this
map.on('pointermove', function(e) {
if (e.dragging) return;
var pixel = e.map.getEventPixel(e.originalEvent);
var hit = map.hasFeatureAtPixel(e.pixel, {
layerFilter: function(layer) {
return layer.get('layer_name') === 'jls';
}
});
e.map.getTargetElement().style.cursor = hit ? 'pointer' : '';
});
Hope it helps :)
In your function, you are basically looping through all the layers at the mouse location. In that loop, if the layer has the proper name you set the pointer, else if it has a different name, you remove the pointer (or set it to something else).
As it is, it is dependent on the layer order:
ex: layer 1 = target -> set custom pointer. Layer 2 = other layer -> remove pointer. ==> final pointer: removed
ex: Layer 1 = other layer -> remove pointer. Layer 2 = target -> set custom pointer. ==> final pointer: custom pointer
The looping occurs when you set the hit variable, i.e. it corresponds to the last layer only as you are overriding the value for each layer.
map.on('pointermove', function(e) {
if (e.dragging) return;
var pixel = e.map.getEventPixel(e.originalEvent);
// initialize the hit variable to false (not found)
var hit = false;
e.map.forEachFeatureAtPixel(pixel, function(feature, layer) {
if ( vectorJLS.get('layer_name') === 'jls') {
//IF we have found the layer, flag it (but don't return anything!)
hit = true;
}
});
e.map.getTargetElement().style.cursor = hit ? 'pointer' : '';
});
Does anyone have a method for adding a title to a leaflet layers control? Just a line of text, for example "Available layers." Ideally I'd like to add a link to the text as well.
I thought it would be simple, but I haven't been able to find a solution. I tried methods similar to this question (radio button/checkbox remains after adding "dummy" layer) and this question (adds div to end of layers, seems more complex than my needs). Unfortunately with my experience level, I haven't been able to connect the dots. Any suggestions?
Input Elements in Layer Control are present under
.leaflet-control-layers-overlays
$(".leaflet-control-layers-overlays").prepend("<label>Available layers</label>");
You can also assign a class and add some styling on this. This is not good solution but hope it helps you.
#Leaflet1.7
Here is the vanilla JS minimal solution.
// 0. Your's WMS layers:
var fooLayers = {
"Aaa" : ign_ari,
"Bbb" : ign_cri,
"Ccc" : ign_grvi
};
// 1. start with Control
var fooLegend = L.control({position: 'topleft'});
fooLegend.onAdd = function () {
var div = L.DomUtil.create('div');
// here is Your part:
div.innerHTML = '<span class='your-class'>Your Title Text</span>';
return div;
};
fooLegend.addTo(map);
var fooCtrl = L.control.layers(fooLayers, null,
{collapsed : false, position: 'topleft'})
.addTo(map);
//
// Nothing unusual, until now:
var fooCtrlDiv = fooCtrl.getContainer();
fooCtrlDiv.insertBefore(fooLegend.getContainer(), fooCtrlDiv.firstChild);
You can assign a title attribute to the control. Here's an obviously incomplete snippet from a current project:
// Add settings button
var atlasMapSettings = L.Control.extend({
options: {
position: 'topleft'
},
onAdd: function (map) {
var control = L.DomUtil.create('div', 'leaflet-bar leaflet-control leaflet-control-custom atlas-settings-control closed');
var icon = L.DomUtil.create('div', 'fa fa-gear closed', control);
var content = L.DomUtil.create('div', 'control-content empty', control);
$(icon).attr('title', 'Map settings');
Leaflet 1.7: Addition to #Mruk's answer: If you want to use collapsed: true, then you have to dig a little bit deeper in the DOM tree:
const fooCtrlDiv = fooCtrl.getContainer();
fooCtrlDiv
.querySelector('.leaflet-control-layers-list')
.insertBefore(
fooLegend.getContainer(),
layerControlDiv.querySelector('.leaflet-control-layers-list').firstChild
);
What are the formats supported by OpenLayers 3?
I need open in OpenLayers a map with different colors, like the image below. So, which format should I export? I'm using QGIS and ArcMap to create the maps.
This map represents the Brazil population by regions (the darker the color, the greater the population). The data is coming from a shapefile where each row represents one different region (5570 regions in total).
Shapefile attribute table:
I solved the problem using the Leaflet API for JavaScript instead of OpenLayers 3.
The result I got is this:
To help me find a solution, I followed the Interactive Choropleth Map tutorial.
1. We're using Leaflet, so we need to import the leaflet.js and leaflet.css files. The Leaflet library can be downloaded here.
<script src="leaflet.js" type="text/javascript"></script>
<link rel="stylesheet" href="leaflet.css" type="text/css" >
2. To generate the map I use a GeoJSON file with the informations of each region. As the data I had were coming from a ShapeFile, I used ArcGIS Online to create the GeoJSON file that I needed.
3. I'm working with JQuery to open the GeoJSON file by Ajax, so is need to import the library. JQuery can be downloaded here. For example:
<script type="text/javascript" src="jquery-3.0.0.js" ></script>
4. The JavaScript code to create the map:
<script type="text/javascript">
// Create a map ('map' is the div id where the map will be displayed)
var map = L.map('map').setView([-15, -55], 5); // Set the center of the map
// Select the Basemap
L.tileLayer('http://stamen-tiles-{s}.a.ssl.fastly.net/toner-lite/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors | CRR'
}).addTo(map);
// Var to save the GeoJSON opened
var geojsonObject;
// Open the GeoJSON with the informations
// Change 'pop_2015_json.geojson' for your GeoJSON file
$.getJSON("pop_2015_json.geojson", function(json) {
geojsonObject = L.geoJson(json, {style: style, onEachFeature: onEachFeature});
geojsonObject.addTo(map);
});
// Function to set the color of each region
function getColor(p) {
return p > 2000000 ? '#023858' :
p > 600000 ? '#045a8d' :
p > 300000 ? '#0570b0' :
p > 90000 ? '#3690c0' :
p > 20000 ? '#74a9cf' :
p > 10000 ? '#a6bddb' :
'#d0d1e6';
}
// Function to apply the style
function style(feature) {
return {
// 'pop_2015' is an information from GeoJSON
fillColor: getColor(feature.properties.pop_2015),
weight: 1,
opacity: 0.9,
color: 'grey',
dashArray: '3',
fillOpacity: 0.9
};
}
// Change the style when mouse are hovered
function highlightFeature(e) {
var layer = e.target;
// Change the border style
layer.setStyle({
weight: 3,
color: '#666',
dashArray: '',
fillOpacity: 0.7,
opacity: 1
});
if (!L.Browser.ie && !L.Browser.opera && !L.Browser.edge) {
layer.bringToFront();
}
// Update the style of the hovered region
info.update(layer.feature.properties);
}
// Reset the style on mouse over the region
function resetHighlight(e) {
geojsonObject.resetStyle(e.target);
info.update();
}
// Zoom to region when clicked
function zoomToFeature(e) {
map.fitBounds(e.target.getBounds());
}
// Apply for each region
function onEachFeature(feature, layer) {
layer.on({
mouseover: highlightFeature,
mouseout: resetHighlight,
click: zoomToFeature
});
}
// Add a field to display the region information
var info = L.control();
info.onAdd = function (map) {
this._div = L.DomUtil.create('div', 'info'); // create a div with a class "info"
this.update();
return this._div;
};
// Method that we will use to update the control based on feature properties passed
info.update = function (props) {
this._div.innerHTML = '<h4>População por Município </h4>' + (props ?
'<b>' + props.nome + '</b><br />' + props.pop_2015 + ' habitantes</sup>'
: ' ');
};
info.addTo(map);
// Lengend of the map
var legend = L.control({position: 'bottomright'});
// Create the legend
legend.onAdd = function (map) {
var div = L.DomUtil.create('div', 'legend'),
// with the interval values
grades = [0, 10000, 20000, 90000, 300000, 600000, 2000000],
labels = [];
// loop through our population intervals and generate a label with a colored square for each interval
for (var i = 0; i < grades.length; i++) {
div.innerHTML +=
'<i class="legenda" style="background:' + getColor(grades[i] + 1) + '"></i> ' +
grades[i] + (grades[i + 1] ? ' – ' + grades[i + 1] + '<br>' : ' +');
}
return div;
};
legend.addTo(map);
</script>
5. Some considerations:
There are another ways to open a GeoJSON file into JavaScript. You can check in this question.
The colors used in the function getColor(p) can be changed the way you want. Take a look in ColorBrewer to help choosing a nice Choropleth color.
Some more informations about using GeoJSON with Leaflet: GeoJSON with Leaflet.
Another way to create Choropleth maps using Leaflet: Choropleth plugin for Leaflet.
Thanks everyone for the help.
So, I'm trying to map bus routes using leaflet w/geojson for the coordinates. I'm having a difficult time with one aspect where, on a click, the bus line is boldened, and, ideally, the last clicked on feature returns to the default style.
What I have so far
function $onEachFeature(feature, layer) {
layer.on({
click: function(e) {
//calls up the feature clicked on
var $layer = e.target;
var highlightStyle = {
opacity: 1,
weight: 5
};
$layer.bringToFront();
$layer.setStyle(highlightStyle);
}
});
}
//imagine all the leaflet map tile code here
//this is where the features get added in and the $oneachfeature function
var busFeature = L.geoJson(busRoutes, {
style: defaultBusRouteColor,
onEachFeature : $onEachFeature
});
busFeature.addTo(map);
Above, what I have now successfully changes the style of the feature to what's in highlightStyle. However, when another feature is clicked, the style remains. How do I remove the previously clicked on feature's style so that only one feature at a time has the style highlightStyle?
Things I've already tried: using addClass/removeClass to jQuery methods, layer.resetStyle() with leaflet, and a bunch of other things that still didn't work. Note: this would ideally be used in a mobile version, as the desktop version uses a hover function that emphasizes the features, with no problem. this:
function $oneachfeature(feature, layer){
layer.on({
mouseover: function (e){makes feature bold}
});
layer.on({
mouseout: function (e){makes feature normal again}
});
}
Any suggestions?
Store a reference to the highlighted layer so you can later call resetStyle on it:
// Variable to store selected
var selected
// Create new geojson layer
new L.GeoJSON(collection, {
// Set default style
'style': function () {
return {
'color': 'yellow',
}
}
}).on('click', function (e) {
// Check for selected
if (selected) {
// Reset selected to default style
e.target.resetStyle(selected)
}
// Assign new selected
selected = e.layer
// Bring selected to front
selected.bringToFront()
// Style selected
selected.setStyle({
'color': 'red'
})
}).addTo(map)
Example: http://embed.plnkr.co/RnQO1s/preview
Reference: http://leafletjs.com/reference.html#geojson-resetstyle
using resetStyle() would seem to be an easier solution...simply reset the style of the layer before applying the new style to the feature. This requires only a sinlge line of code adding to your original function:
function $onEachFeature(feature, layer) {
layer.on({
click: function(e) {
//calls up the feature clicked on
var $layer = e.target;
var highlightStyle = {
opacity: 1,
weight: 5
};
busFeature.resetStyle();
$layer.bringToFront();
$layer.setStyle(highlightStyle);
}
});
}
Remove previous Highlight before adding the next:
.removeLayer() works to remove the previously set geoJSON selection using .addTo()
theMap = yourMap.Map
geoJson = yourMap.geoJSON();
onclick() {
const highlightedFeature = {
'color': '#12FF38',
'fillColor': '#30D8E0',
'fillOpacity': 0.3
};
this.theMap.removeLayer(this.geoJson);
this.geoJson = yourMap.geoJSON( Feature, {
style: highlightedFeature
});
this.geoJson.addTo(this.theMap);
}
I'm trying to produce a mapping application with Bing Maps with a button that will retrieve a JSON string and places pins on the map based on the center of the map.
That is working fine, but I'm running into two issues that I'm having trouble diagnosing.
The first is that when I move the map after placing the pins, the majority of them disappear from the map except for 1-3. I've figured out that the pins are still being held in map.entities, but just aren't all displaying.
The second issue is that I have a click event on the pins, and sometimes when I click on a pin it will disappear (and sometimes reappear elsewhere on the map).
Here is my code:
function addPin() {
map.entities.clear();
var pinImg = "images/MapPin.jpg";
var latLong = {};
var name;
for (var i = 0; i < factualJson.response.data.length; ++i) {
latLong['latitude'] = factualJson.response.data[i].latitude;
latLong['longitude'] = factualJson.response.data[i].longitude;
name = factualJson.response.data[i].name;
var pin = new Microsoft.Maps.Pushpin(latLong, {
icon: pinImg,
anchor: new Microsoft.Maps.Point(latLong['latitude'], latLong['longitude']),
draggable: true,
width: 48,
height: 48
});
Microsoft.Maps.Events.addHandler(pin, 'click', displayName);
pin.title = name;
pin.id = 'pin' + i;
map.entities.push(pin);
}
document.getElementById("arrayLength").innerHTML = "Number of locations: " + map.entities.getLength();
}
function displayName(e) {
document.getElementById("name").innerHTML = "";
if (this.target.id != -1) {
document.getElementById("name").innerHTML = this.target.title;
}
}
function boot() {
Microsoft.Maps.loadModule('Microsoft.Maps.Overlays.Style', { callback: getMap });
}
function getMap() {
map = new Microsoft.Maps.Map($gel("bingMap"), {
credentials: getKey(),
customizeOverlays: true,
enableClickableLogo: true,
enableSearchLogo: true,
showDashboard: true,
showBreadcrumb: true,
showCopyright: true,
zoom: 10,
labelOverlay: Microsoft.Maps.LabelOverlay.hidden
});
setGeoLocation();
//setTimeout(optimizeMap, 100);
window.onresize = resizeWin;
resizeWin();
}
Currently I make an ajax call from the button, and the callback function calls 'AddPin' which adds the pins to the map. I thought I'd add in the map initialization code in case it was relevant. Currently boot() is called on body load.
For me the solution was similar to yours #canadian coder
Microsoft.Maps.Location() only accepts float values, no strings and Int.
I use MVC architecture and passed a string using a model. Later i converted that string to float and passed to Location.
Problem solved.
var pushpin = new Microsoft.Maps.Pushpin(
center, { icon: '/Content/BingPushpin.png', width: 50, height: 50, draggable: false });
pushpin.setLocation(new Microsoft.Maps.Location
(parseFloat(#Model.Latitude) , parseFloat(#Model.Longitude)));
dataLayer.push(pushpin);
locations.push(new Microsoft.Maps.Location
(parseFloat(#Model.Latitude) , parseFloat(#Model.Longitude)));
EDIT :
Later found out that problem still exist. Another reason can be that you are calling that Map to load twice. So check for any other instance of the map which is being loaded. In my case see below.
$(document).ready(function () {
loadSearchModule(); //calling Map to Load at a div without pushpins
//code to do something
getMapOnLocation();
}
function getMapOnLocation()
{//code to display map with pushpin
}
In the Above example I was telling the control to load my map with PushPins and when the page is fully Loaded load the map without pushpins.
Hope this helps :)
As always I need to ask the question before I figure it out myself.
The issue was that I needed to push a Microsoft location object into the pin and not an object. Like so:
var loc = new Microsoft.Maps.Location(47.592, -122.332);
And NOT my latLong object.
This also seemed to fix the issue of disappearing pins on click event.