Conditionally style Leaflet circleMarkers to be rectangles - javascript

I am creating a Leaflet map from GeoJSON data. I want to make some Leaflet markers to be a different shape based on if the geoJSON has images (this is indicated under feature.properties, along with some other HTML for a pop-up). The default shape is an L.circleMarker and I want the relevant markers to turn into rectangles. Unfortunately L.circleMarker does not have a default option to change the shape.
I tried this plugin https://github.com/masajid390/BeautifyMarker but it was really buggy for my purposes. The code just seemed to be applying CSS to markers anyway. See this issue: https://github.com/masajid390/BeautifyMarker/issues/20 where it was suggested to style markers into rectangles with iconStyle : 'border-radius:0!important'. Returning to ordinary Leaflet, if I could get at the CSS for an individual circle I could easily change it by setting the CSS:
.rectangle_marker {
border-radius: 0
}
One approach would be to add a class to the relevant markers, and then style them.
I am already applying some conditional styling as follows, directly setting the L.circleMarker's options:
function style(feature) {
options = drawOptions(feature.properties);
// mark items that have images
if (feature.properties.First) {
options.radius = 11
options.width = 3
}
// mark filter options with different colors
if (feature.properties.Filter) {
if (feature.properties.Filter.toUpperCase() === "BEFORE") {
options.fillColor = "#29A7B3"
} else if (feature.properties.Filter.toUpperCase() === "DURING") {
options.fillColor = "#DC4A35"
} else if (feature.properties.Filter.toUpperCase() === "AFTER") {
options.fillColor = "#215E57"
}
}
return options
}
(in turn, drawOptions looks like this:)
function drawOptions(props) {
return {
radius: 7,
color: "black",
width: 1,
opacity: 1,
fillColor: "#784889",
fillOpacity: 1
}
}
I tried this way: How to use the addClass method in leaflet maker?, but it doesn't work because L.circleMarker doesn't have a built in option like className to set a class. How to add a marker with a specific class to a layer or layergroup? would not work for the same reason.
Tried the following code to indirectly add a class based on How do you add a class to a Leaflet marker?:
function pointToLayer(feature, latlng) {
const marker = L.circleMarker(latlng, drawOptions(feature.properties));
if (check_images(feature.properties)) {
marker._path.classList.add("rectangle_marker")
}
return marker
}
But it doesn't work, because the marker has no attribute _path before being added to the map.
Since I am adding the entire layer to the map, I could conceivably pull out the relevant markers in between creating the geoJSON and adding them to the map, but I'm not sure how:
window.geojson = L.geoJSON(json, {
onEachFeature: onEachFeature,
pointToLayer: pointToLayer,
style: style
});
//What would go here?
window.geojson.addTo(window.map);
Is there any other approach I could take to get some of my L.circleMarkers to appear as squares?

I gave up and used divIcons instead. You can add classNames and add css instead.
function markerOptions(props) {
//default options
const options = {
iconSize: [15, 15],
//mark the center of the icon
iconAnchor: [7, 7],
}
// if location has images, make marker a rectangle
if (check_images(props)) {
options.className = "marker-images"
}
// mark filter options with different colors
if (props.Filter) {
if (props.Filter.toUpperCase() === "BEFORE") {
options.className += " options-before"
} else if (props.Filter.toUpperCase() === "DURING") {
options.className += " options-during"
} else if (props.Filter.toUpperCase() === "AFTER") {
options.className += " options-after"
}
}
//enlarge first location
if (props.First) {
options.className += " first"
options.iconSize = [21, 21]
options.iconAnchor = [10, 10]
}
return options
}
function pointToLayer(feature, latlng) {
return L.marker(latlng, { icon: L.divIcon(markerOptions(feature.properties)) });
}
Then in your css file you can change the border, border-radius, background-color, and other options that are not built in to L.divIcon.

Related

Leaflet: using mouseover and opacity get a conflict

I created a function to mouse over a colorless layer and everything. So far so good, and also created a function to go back as it was before the mouse. But when I use the opacity, the reset function does not return to normal and leaves the opacity in the default state (0.7) and not in the state it is in at the moment.
Function to mouseouver:
function highlightFeature_stComerciais(e) {
layerStComerciais = e.target;
layerStComerciais.setStyle({
weight: 5,
color: '#666',
dashArray: ''
});
info.update(layerStComerciais.feature.properties);
}
function to mouseout
function resetHighlight_stComerciais(e) {
setoresComerciaisOverlay.resetStyle(e.target);
info.update();
}
opacity:
$('#sldOpacity').on('change', function(){
$('#image-opacity').html(this.value);
setoresComerciaisOverlay.setStyle({ fillOpacity: this.value })
});
The default opacity is 0.7, assuming I put 0 of opacity, when I do mouse over the Layer all right, but when I mouse out it returns to 0.7 and I do not want this. Why my reset doesnt work how I want? Thanks!
I guess you build your vector layers using GeoJSON data and L.geoJSON factory, that returns a Leaflet GeoJSON Layer Group.
The resetStyle method on that group re-applies the style that was applied at the time of creation of that group, or the default one if you did not provide style:
Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
If you later change the style of one or all of the vector layers in that group, it will be therefore overridden when you use resetStyle, and the initial style will be applied.
An easy workaround is simply to modify the GeoJSON Layer Group's style option as well. However, it will affect all its child layers.
group.options.style = newStyle;
Another solution would be to record the new style of each vector layer, and use that recorded value when you want to restore the previous state, instead of using the group's resetStyle method.
var map = L.map("map").setView([48.85, 2.35], 12);
var geojson = {
type: "Feature",
geometry: {
type: "Point",
coordinates: [2.35, 48.85]
}
};
var point;
var startStyle = {
color: "red"
};
var newStyle = {
color: 'green'
};
var group = L.geoJSON(geojson, {
style: startStyle,
pointToLayer: function(feature, latlng) {
point = L.circleMarker(latlng);
assignStyle(point, startStyle);
return point;
}
}).addTo(map);
// Record the style to the individual vector layer if necessary.
function assignStyle(leafletLayer, newStyle) {
leafletLayer.setStyle(newStyle);
leafletLayer._recordedStyle = newStyle;
}
// When desired, apply the style that has been previously recorded.
function reassignStyle(leafletLayer) {
leafletLayer.setStyle(leafletLayer._recordedStyle);
}
document.getElementById('button').addEventListener('click', function(event) {
event.preventDefault();
// Either use the wrapper function that records the new style…
//assignStyle(point, newStyle);
// Or simply modify the group's style option, if it is acceptable affecting all child layers.
point.setStyle(newStyle);
group.options.style = newStyle;
});
group.on({
mouseover: function(e) {
e.target.setStyle({
color: 'blue'
});
},
mouseout: function(e) {
//reassignStyle(e.layer);
group.resetStyle(e.layer);
}
});
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.0.3/dist/leaflet.css">
<script src="https://unpkg.com/leaflet#1.0.3/dist/leaflet-src.js"></script>
<div id="map" style="height: 100px"></div>
<button id="button">Change color to Green…</button>
<p>…then mouseover and mouseout</p>

Openlayers3, vector data layer not visible after proceeding select onchange() function

I input two polygon geojson layers, one is just boundary layer, another is the vectorlayer contains many attributes.
I add map.on('click', function(evt), so users can click on the map, and the corresponding feature will be highlighted (with another style). I did this by add a new overlay layer with only this feature.
I also have a Select with many options and a onchange() function. The onchange() functions let style of vectorlayer change based on the value of select option.
The click and highlight feature thing works well at begining, but when I change the value of the select and the style of vectorlayer changes, the highlight does not work.
I also tried to not using the onchange() function for select, then the highlight works well again.
I'm quite confused about this... Why does this two thing could not work together? Thank you.
Example of select:
<select id="vai_year" onchange="get_vaiSelect(vectorLayer)">
<option value="VAI_2013">2013年</option>
<option value="VAI_2012">2012年</option>
<option value="VAI_2011">2011年</option>
</select>
onchange function:
function get_vaiSelect(layer)
{
featureOverlay.setStyle(highlightStyle);
featureOverlay.setZIndex(50);
map.addLayer(featureOverlay);
features = layer.getSource().getFeatures();
length = features.length;
attributes = layer.getSource().getProperties();
var vaivalue = document.getElementById("vai_year");
for(var f=0;f < length; f++)
{
if (vaivalue.value == 'VAI_2013')
{
level = features[f].getProperties().vai_2013+1;
}
else if (vaivalue.value == 'VAI_2012')
{
level = features[f].getProperties().vai_2012+1;
}
...
else if (vaivalue.value == 'VAI_1992')
{
level = features[f].getProperties().vai_1992+1;
}
styleCache[level] = new ol.style.Style
({
fill: new ol.style.Fill({color: vai_levels[level],}),
});
features[f].setStyle(styleCache[level]);
}
};
highlight style:
highlightStyle = function(feature, resolution)
{
var text = resolution < 5000 ? feature.get('NL_NAME_3') : '';
highlightStyleCache = new ol.style.Style(
{
stroke: new ol.style.Stroke(
{
color: 'rgba(166,18,12,1)',
width: 1.2
}),
fill: new ol.style.Fill(
{
color: 'rgba(255,255,128,0.8)'
}),
text: new ol.style.Text(
{
font: '14px Calibri,sans-serif',
text: text,
color:'rgba(0,0,100,1)'
})
});
return highlightStyleCache;
}
overlay layer:
featureOverlay = new ol.layer.Vector(
{
source: new ol.source.Vector(),
style: highlightStyle,
ZIndes: 60
});
displayFeatureInfo = function(m)
{
featureOverlay.setZIndex(70);
vectorLayer.setZIndex(40);
map.addLayer(featureOverlay);
feature= map.forEachFeatureAtPixel
(
m,
function (feature, vectorLayer)
{
return feature;
}
/* function(layer)
{
return layer === vectorLayer;
}*/
);
if (feature !== highlight)
{
if (highlight)
{
featureOverlay.getSource().removeFeature(highlight);
}
if (feature)
{
featureOverlay.getSource().addFeature(feature);
}
highlight = feature;
}
};
map.onclick:
map.on('click', function(evt)
{
if (evt.dragging)
{
return;
}
pixel = map.getEventPixel(evt.originalEvent);
displayFeatureInfo(pixel);
You don't have to add a new layer every time you click the map, it is enough if you just change the source of the desired layer, as you are doing at the end of the displayFeatureInfo function.
Can you please provide us a jsfiddle working example, so that we can debug your code and see more details which are not shown now? (We don't see, for example, vectorLayer definition, or the polygons you mentioned at the beginning of your question)

OpenLayers 3 supported formats

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.

OpenLayers 3 show different highlight style based on layer type

I have a map with two layers. One layers display points (circles) and another layer display an icon on the map.
I would like two have to highlight styles so that when then user clicks on a feature it shows the correct highlight style based on the layer type.
Right now I have created two highlight styles. I also have a 'select' event which selects the feature clicked. When I click on a new feature the "old" feature does not remove the highlight style.
So far I have this:
var highlightOne = function () {
var scaleRadius = map.getView().getZoom() >= 7 ? 15 : 5;
return [new ol.style.Style({
image: new ol.style.Circle({
radius: scaleRadius,
fill: new ol.style.Fill({
color: '#fff'
}),
stroke: new ol.style.Stroke({
color: '#658acf',
width: 2
})
}),
zIndex: 1
})]
}
var highlightTwo = function () {
var scaleRadius = map.getView().getZoom() >= 7 ? 15 : 15;
return [new ol.style.Style({
image: new ol.style.RegularShape({
radius: scaleRadius,
points: 4,
rotation: 0.8,
rotateWithView: true,
fill: new ol.style.Fill({
color: '#fff'
}),
stroke: new ol.style.Stroke({
color: '#658acf',
width: 2
})
}),
zIndex: 1
})]
}
var selectInteraction = new ol.interaction.Select({
condition: ol.events.condition.singleClick,
style: highlightStyle
});
selectInteraction.on('select', function (e) {
var selected = e.selected,
deselected = e.deselected;
if (selected.length) // Selected feature
{
selected.forEach(function (feature) {
// Get the layer name to apply custom highlight style
if (feature.getLayer().get('name') == "layerone")
{
feature.setStyle(highlightTwo)
} else {
feature.setStyle(highlightOne)
}
})
} else {
deselected.forEach(function (feature) {
// Remove highlight from the other features
feature.setStyle(null)
})
}
});
In general, you want to avoid setting the styles on the features when setting a temporary display style (as you do with the select interaction). Setting style functions on layers or interactions are not only more memory efficient, it's also takes any responsability of unsetting the style from you.
I would suggest that you look at a solution like
var selectInteraction = new ol.interaction.Select({
condition: ol.events.condition.singleClick,
style: function(feature, resolution){
if (feature.getLayer().get('name') == "layerone") {
return highlightTwo()
} else {
return highlightOne()
}
}
});
As a side note, I would suggest using the resolution parameter to the style function rather than polling the map for the current zoom level. When setting a style on the feature with a radius based on the current zoom level, the style will not update when zooming in or out.

Adding/removing L.control from leaflet.js map

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='';

Categories