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.
Related
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.
I am new to JS and I'm seeking for help because I am at dead end. I struggle for a couple of days on events in JS / LeafletJS :(
I want to highlight the element on the map and corresponding data listed in div. I don't know how to select both and do an event on mouseover and mouseout to highlight it
I've stopped on highlight object on map but with no success with div :(
function highlightFeature(e) {
var layer = e.target;
layer.setStyle({
color: '#666' ,
fillOpacity: 0.7
})
}
function resetHighlight(e) {
geoJSON.resetStyle(e.target);
};
function onEachFeature(feature, layer) {
layer.on({
mouseover : highlightFeature,
mouseout: resetHighlight
});
};
Here's JSFiddle of what I've done so far.
https://jsfiddle.net/JohnDoeJr/jcxz2ruw/6/
I expect that when you put the mouse over multipolygon it will change appearance (highlight) and in div the corresponding data will also highlight and vice versa.
Just add an id to each layer and the assign same id to the corresponding text
geoJSON.eachLayer(function (layer) {
layer._path.id = layer.feature.properties.Name; // This assigns id to each layer with the layer name
);
function updateList(target){
var displayed = target.getLayers();
var list = document.getElementById('displayed-list');
list.innerHTML = "";
displayed.forEach(function(borders){
var li = document.createElement('li');
li.innerHTML = borders.feature.properties.Name;
li.setAttribute("id", borders.feature.properties.Name); // this assigns id to the text with layer's name
list.appendChild(li);
});
}
And add these style on hover over th geoJSON layer
function highlightFeature(e) {
var layer = e.target;
layer.setStyle({
color: '#666' ,
fillOpacity: 0.7
})
id = e.target.feature.properties.Name
$("li#"+id).css("font-weight","bold")
}
function resetHighlight(e) {
geoJSON.resetStyle(e.target);
id = e.target.feature.properties.Name
$("li#"+id).css("font-weight","")
};
And don't forget to include jQuery.
You can add the same function to text on hover which I have not included.
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>
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 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='';