How to change style in geojson vector layer with multipolygons - openlayers-3

I am dealing with a map on which vector layers come from different geojson files. Each file contains a series of polygons (type = multipolygon).
Each polygon is characterised by a series of parameters, such as "species". By default layers are set non visible and have a certain style (fill and stroke).
I created a select to enable the search by species
<form>
<select class="species">
<option value="">Choose</option>
<option value="Balaenopteraphysalus">Balaenoptera physalus</option>
<option value="Physetercatodon">Physeter catodon</option>
<option value="Delphinusdelphis">Delphinus delphis</option>
<option value="Tursiopstruncatus">Tursiops truncatus</option>
<option value="Stenellacoeruleoalba">Stenella coeruleoalba</option>
<option value="Grampusgriseus">Grampus griseus</option>
<option value="Globicephalamelaena">Globicephala melaena</option>
<option value="Ziphiuscavirostris">Ziphius cavirostris</option>
<option value="Monachusmonachus">Monachus monachus</option>
</select>
</form>
<button id="clearSpecies">Clear</button>
I then wrote a jquery that enables to upload the layers only if the selected species is present in the file
$(document).ready(function() {
$("select.species").change(function() {
var selectedSpecies = $(".species option:selected").val();
if (selectedSpecies) {
//geojson request
$.getJSON('http://localhost:8888/maps/prova/immas_test_separated_js_immas_file/resources/test_imma_2.geojson', function (data) {
$.each(data.features, function (key, val) {
$.each(val.properties, function(i,j){ //i = proprietà l = valore proprietà
if(i == 'Species') {
//replace spaces to have one single word
j = j.replace(/\s+/g, '');
species = j.split(",");
species.forEach(function(animal) {
if(animal == selectedSpecies) {
//test passed
prova1.setVisible(true);
//add something to change style (hide the multipolygon not which are not satisfying the condition and show those who satisfy the condition
}
});
}
});//loop ends
});//loop ends
});
//ends geojson request
}
});
//clears layers
$("#clearSpecies").click(function(){
prova1.setVisible(false);
});
});
Everything works fine. However, since I actually deal with different polygons in the same layer, I need a further step.
After setting the layer as visible in the loop above (prova1.setVisible(true);
), I need to change style not to the whole layer, but to single polygons ccording to the if condition: those in which the parameter "species" do not contain the selected option value must change fill and stroke to none (transparent), while the polygons for which the parameter species contains the selected option value must be filled with color.
Consider that the parameter "species" in the geojson file contains more than one name (ex. "Species": "Monachus monachus, Balaenoptera physalus, Physeter macrocephalus, Ziphius cavirostris, Globicephala melas, Grampus griseus, Tursiops truncatus, Stenella coeruleoalba, Delphinus delphis")
Any suggestion? Thanks!
===========
UPDATE
===========
I followed #pavlos suggestion an studied the example reported here. However I didn't come through.
Here the code I used for the styles and to create the function styleFunction(feature, resolution)
//SET STYLES
//set colours
var colourSpecies = [64,196,64,1];
var colourCriteria = [90,160,64,1];
//set levelnames related to colours
var selectedLevels = {
'species': colourSpecies,
'criteria': colourCriteria
}
//default style
var defaultStyle =
new ol.style.Style({
fill: new ol.style.Fill({
color: [0,0,0,1]
}),
stroke: new ol.style.Stroke({
color: [0,0,0,1],
width: 1
})
});
//custom styleFunction
var styleCache = {};
function styleFunction(feature, resolution) {
var level = feature.get('selectedLevel');
if (!level || !selectedLevels[level]) {
return [defaultStyle];
}
if (!styleCache[level]) {
styleCache[level] =
new ol.style.Style({
fill: new ol.style.Fill({
color: selectedLevels[level]
}),
stroke: defaultStyle.stroke
});
}
return [styleCache[level]];
}
I hence tried to rewrite the JQuery/javascript for handling the style change, when the user search for a species. In doing this I didn't use the forEachFeature function as I never succeeded in getting any results, but I looped inside the objects via jQuery. The various loops are due because the "species" parameter contains a string with different names (genus + species), otherwise I could have overcomed the problem by copying exactly the example given in the link above. As said previously, I would like to highlight with a differen style those polygons which contain in the string the species searched with the select (better would be to hightlight these and hide all the others not containing the species searched).
$(document).ready(function() {
$("select.species").change(function() {
var selectedSpecies = $(".species option:selected").val();
if (selectedSpecies && selectedSpecies !='') {
//geojson request
$.getJSON('http://localhost:8888/maps/prova/immas_test_separated_js_immas_file/resources/test_imma_2.geojson', function (data) {
{$.each(data.features, function (key, val) {
console.log(val.properties);
$.each(val.properties, function(i,j){ //i = proprietà l = valore proprietà
console.log(i);
if(i == 'Species') {
j = j.replace(/\s+/g, ''); //eliminates spaces between genus and species
var species = j.split(",");
console.log(species);
var species_array_length = species.length;
console.log(species_array_length);
var counter;
for (counter = 0; counter < species_array_length; counter++){
if (selectedSpecies === species[counter]){
var animal = species[counter];
console.log('Found' + animal);
var feature = val.properties;
console.log(feature);
feature.set('selectedLevel', 'species');
}
}//termina ciclo for
}
});//termina loop
});//temrina loop}
});
//ends geojson request
prova1.setVisible(true);
}
});
//clears layers
$("#clearSpecies").click(function(){
prova1.setVisible(false);
});
});
However it doesn't work: an error with feature.set('selectedLevel', 'species'); is displayed and again all layers are uploaded with the default style. I am quite worried as this is a simple example. At the end I should deal with something like 18 geojson files and two selections (by species and by "criteriacode" which is another parameter inside my geojson file).
I add here a link to the files used (included teh geojson used as test)

This an ====UPDATE=== to the question posted above.
thanks to some suggestions provided by #pavlos, I succeded in solving the problems above.
The whole solution with test files is available at this fiddle.
SETTING STYLES TO THE LAYER
//SET STYLES
//default style
var defaultStyle =
new ol.style.Style({
fill: new ol.style.Fill({
color: [0,0,0,1]
}),
stroke: new ol.style.Stroke({
color: [0,0,0,1],
width: 1
})
});
var selectStyle = new ol.style.Style({
fill: new ol.style.Fill({
color: [64,196,64,1]
}),
stroke: new ol.style.Stroke({
color: [64,196,64,1],
width: 1
})
});
var transparentStyle = new ol.style.Style({
fill: new ol.style.Fill({
color: [255,255,255, 0]
}),
stroke: new ol.style.Stroke({
color: [255,255,255, 0],
width: 1
})
});
//Gets the layer sources
var ocean_map =
new ol.layer.Tile({
source: new ol.source.XYZ({
url: 'https://services.arcgisonline.com/ArcGIS/rest/services/' +
'Ocean_Basemap/MapServer/tile/{z}/{y}/{x}',
}),
visible: true,
});
var source =
new ol.source.Vector({
format: new ol.format.GeoJSON({
}),
dataProjection: 'EPSG:3857',
url: 'test.geojson',
attributions: [
new ol.Attribution({
html: 'Mediteranean region'
})
]
});
var prova1 =
new ol.layer.Vector({
source: source,
style: defaultStyle,
name: 'Mediteranean region',
visible: false,
});
MANAGING THE STYLES
//when clicking the select
document.getElementById("species_select").addEventListener('click', function () {
resetSelectElement(mySelect);
//first step it loads the vector layer and sets the style to transparent
prova1.setStyle(transparentStyle);
prova1.setVisible(true);
//second step when the select changes (i.e. I make a choice)
$("#species_select").change(function() {
//it starts the function to change styles according to the selection made
var selectedSpecies = $("#species_select option:selected").text();
console.log('selectedSpecies',selectedSpecies);
source.forEachFeature(function(feat){
console.log(feat);
console.log(feat.get('Species'))
console.log(feat.get('Species').indexOf(selectedSpecies));
//if substring(selected text) exist within fetaure property('Speices)
//should return any value other than -1
if (feat.get('Species').indexOf(selectedSpecies)!=-1) {
//so set the style on each feature
feat.setStyle(selectStyle);
} else {
//and if doesnt exist switch back to the deafult style
feat.setStyle(transparentStyle);
}
})
});
});
//clears layers
$("#clearSpecies").click(function(){
prova1.setVisible(false);
prova1.setStyle(defaultStyle);
});

Related

ol3 ext-ol how can make cluster for different layers

I'm using ol3/ol4 with ol-ext
I create two layer:
clusterSource = new ol.source.Cluster({
distance: distanceFt,
source: new ol.source.Vector()
});
// Animated cluster layer
clusterLayer = new ol.layer.AnimatedCluster({
name: 'Cluster',
source: clusterSource,
animationDuration: 700, //$("#animatecluster").prop('checked') ? 700 : 0,
// Cluster style
style: getStyle
});
layersArray.push(clusterLayer); // adding to array
sourceReclamos_Eventos = new ol.source.Cluster({
distance: distanceFt,
source: new ol.source.Vector()
});
capaReclamos_Eventos = new ol.layer.AnimatedCluster({
name: "Reclamos_Eventos",
source: sourceReclamos_Eventos,
animationDuration: 700,
style: getStyle
});
layersArray.push(capaReclamos_Eventos);
Later, add that layers in:
selectCluster = new ol.interaction.SelectCluster({
layers: arraySelectCLuster,
// Point radius: to calculate distance between the features
pointRadius: 20,
animate: true, //$("#animatesel").prop('checked'),
// Feature style when it springs apart
featureStyle: featureStyle,
selectCluster: false, // disable cluster selection
});
After load the layers, only persist the Features in the first layer, in the second layer the Features is removed (clear) after zoom changing... why?
please, help
EDIT
I'm adding features using clusterLayer.getSource().addFeatures() and capaReclamos_Eventos.getSource().addFeatures().
function addFeatures_Reclamos_Eventos(ffs, centrar) {
var transform = ol.proj.getTransform('EPSG:4326', 'EPSG:3857');
var features = [];
for (var i = 0; i < ffs.length; i++) {
features[i] = new ol.Feature();
features[i].setProperties(ffs[i]);
var geometry = new ol.geom.Point(transform([parseFloat(ffs[i].lon), parseFloat(ffs[i].lat)]));
features[i].setGeometry(geometry);
}
qweFeature = features;
capaReclamos_Eventos.getSource().addFeatures(features);
removeloading('mapLoading');
if (document.getElementById('botonFiltrar')) {
document.getElementById('botonFiltrar').disabled = false;
}
if (centrar) {
window.setTimeout(function () {
var extent = capaReclamos_Eventos.getSource().getExtent();
map.getView().fit(extent, map.getSize());
}, 700);// 1/2 seg
}
}

Anystock comparisonMode same value in tooltip

When using a stock chart, I am using the comparisonMode with a date. The value displayed by the crosshair is correct, but the value in the tooltip is the real value (not compared). How could I display the compared value instead?
As you can see on the picture, the compared value is 107.1 but the tooltip is displaying the actual value 893.5. I am using anychart 8.0.0
I'm glad to inform you that in the new version of AnyStock 8.1.0 the calculated change value is available right from the point information. It may be used in tooltips and legends. I guess this is exactly what you were looking for.
The example of using this feature you may find on this link.
Now the context of every point includes valueChange and valuePercentChange properties.
This feature requires a few additional lines of JS code, I prepared an example below to show how it works. Now compared value is shown in cross-hair label, in the tooltip, and in legend.
anychart.onDocumentReady(function() {
var dataTable = anychart.data.table();
dataTable.addData(get_dji_daily_short_data());
var firstMapping = dataTable.mapAs({'value': 1});
var secondMapping = dataTable.mapAs({'value': 3});
chart = anychart.stock();
var plot = chart.plot();
var series0 = plot.line(firstMapping);
var series1 = plot.line(secondMapping);
var yScale = plot.yScale();
// Set comparison mode.
yScale.comparisonMode("value");
var xScale = chart.xScale();
chart.container("container");
chart.draw();
//reference points of both series
var firstVisibleValue0 = null;
var firstVisibleValue1 = null;
//after chart rendering format tooltip and legend
getVisibleValues();
tooltipLegendFormat(firstVisibleValue0, firstVisibleValue1);
//after every scroll change recalculate reference points
//and reformat tooltip and legend
chart.scroller().listen('scrollerchange', function() {
getVisibleValues();
tooltipLegendFormat(firstVisibleValue0, firstVisibleValue1);
});
function getVisibleValues() {
// Gets scale minimum.
var minimum = xScale.getMinimum();
//select data from mappings
var selectable0 = firstMapping.createSelectable();
var selectable1 = secondMapping.createSelectable();
// Sets value for search.
var select0 = selectable0.search(minimum, "nearest");
var select1 = selectable1.search(minimum, "nearest");
// get values in first visible points
firstVisibleValue0 = select0.get('value');
firstVisibleValue1 = select1.get('value');
}
function tooltipLegendFormat(firstVisibleValue0, firstVisibleValue1) {
//format tooltips and legends of both series
series0.tooltip().format(function () {
return 'Series 0: ' + Math.round(this.value - firstVisibleValue0);
});
series0.legendItem().format(function(){
return 'Series 0: ' + Math.round(this.value - firstVisibleValue0);
});
series1.tooltip().format(function () {
return 'Series 1: ' + Math.round(this.value - firstVisibleValue1);
});
series1.legendItem().format(function(){
return 'Series 1: ' + Math.round(this.value - firstVisibleValue1);
});
}
});
html, body, #container {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
<script src="https://cdn.anychart.com/releases/8.0.1/js/anychart-base.min.js"></script>
<script src="https://cdn.anychart.com/releases/8.0.1/js/anychart-stock.min.js"></script>
<script src="https://cdn.anychart.com/releases/8.0.1/js/anychart-exports.min.js"></script>
<script src="https://cdn.anychart.com/releases/8.0.1/js/anychart-ui.min.js"></script>
<script src="https://cdn.anychart.com/csv-data/dji-daily-short.js"></script>
<link rel="stylesheet" href="https://cdn.anychart.com/releases/8.0.1/css/anychart-ui.min.css" />
<div id="container"></div>

openlayers 3 featureKey exists in featureChangeKeys

I have an ol.interaction.Select acting on an ol.source.Vector which is within an ol.layer.Vector. I can select and unselect individual countries fine. I am using a dragbox to select multiple countries. If I select anywhere outside of the multiply selected countries, the currently selected get unselected. Excellent!
However, the problem is that if I select a currently selected country within the multiple, I get the AssertionError: Assertion failed: featureKey exists in featureChangeKeys
Here's my Vector layer:
_countrySelectSource = new ol.source.Vector({
url: 'vendor/openlayers/geojson/countries.json',
format: new ol.format.GeoJSON()
});
var countryLayer = new ol.layer.Vector({
title: 'Country Select',
visible: true,
type: 'interactive-layers',
source: _countrySelectSource
});
I add countryLayer to my map, _map.
I then create a _CountrySelect object that allows me to setActive(true|false) on the interactions related to my country selection.
_CountrySelect = {
init : function(){
this.select = new ol.interaction.Select();
_map.addInteraction(this.select);
this.dragbox = new ol.interaction.DragBox({
condition: ol.events.condition.platformModifierKeyOnly
});
_map.addInteraction(this.dragbox);
this.setEvents();
},
setEvents: function(){
var selectedFeatures = this.select.getFeatures();
var infoBox = document.getElementById('info');
var selfDragbox = this.dragbox;
selfDragbox.on('boxend', function() {
// features that intersect the box are added to the collection of
// selected features, and their names are displayed in the "info"
// div
var extent = selfDragbox.getGeometry().getExtent();
_countrySelectSource.forEachFeatureIntersectingExtent(extent, function(feature) {
selectedFeatures.push(feature);
_countryCodes.push(feature.getId());
});
infoBox.innerHTML = _countryCodes.join(', ');
});
// clear selection when drawing a new box and when clicking on the map
selfDragbox.on('boxstart', function() {
selectedFeatures.clear();
infoBox.innerHTML = ' ';
});
_map.on('singleclick', function(event) {
selectedFeatures.clear();
_countryCodes = [];
_map.forEachFeatureAtPixel(event.pixel,function(feature){
selectedFeatures.push(feature);
var id = feature.getId();
var index = _countryCodes.indexOf(id);
if ( index === -1 ) {
_countryCodes.push(feature.getId());
}
});
infoBox.innerHTML = _countryCodes.join(', ');
});
},
setActive: function(active){
this.select.setActive(active);
this.dragbox.setActive(active);
}
};
_CountrySelect.init();
I am not sure if this is an issue with OL3 or my code. Maybe there's an event I'm not handling? Maybe it's the ol.interaction.DragBox (no luck researching on the DragBox). Let me know what further information I can provide.

How to show one label per multi-polygon in open layers 3?

I'm having an issue trying to figure out how to show one label per multipolygon in OL3. It currently shows the label for every polygon which is not ideal under any circumstance in this particular scenario.
var vector = new ol.layer.Vector({
source: new ol.source.Vector({
format: new ol.format.GeoJSON(),
projection: 'EPSG:4326',
url: 'resources/ol3/countries.geojson'
}),
style: function (feature, resolution) {
style.getText().setText(resolution < 10000 ? feature.get('NAME') : '');
style.getFill().setColor('rgba(255, 255, 255, 0)');
return styles;
}});
I'd like to show the label on the largest polygon if possible.
One more option for the client side is to label just the bigger one, out of the polygon parts of the multipolygon.
For this option you dont need any control on the server-side. So use the following code or go direclty to the fiddle to see it in action:
var vector = new ol.layer.Vector({
style: function (feature, resolution) {
var polyStyleConfig = {
stroke: new ol.style.Stroke({
color: 'rgba(255, 255, 255, 1)',
width: 1
}),
fill: new ol.style.Fill({
color: 'rgba(255, 0, 0,0.3)'
})
}
var textStyleConfig = {
text:new ol.style.Text({
text:resolution < 100000 ? feature.get('NAME') : '' ,
fill: new ol.style.Fill({ color: "#000000" }),
stroke: new ol.style.Stroke({ color: "#FFFFFF", width: 2 })
}),
geometry: function(feature){
var retPoint;
if (feature.getGeometry().getType() === 'MultiPolygon') {
retPoint = getMaxPoly(feature.getGeometry().getPolygons()).getInteriorPoint();
} else if (feature.getGeometry().getType() === 'Polygon') {
retPoint = feature.getGeometry().getInteriorPoint();
}
console.log(retPoint)
return retPoint;
}
}
var textStyle = new ol.style.Style(textStyleConfig);
var style = new ol.style.Style(polyStyleConfig);
return [style,textStyle];
},
source: new ol.source.Vector({
url: 'http://openlayers.org/en/v3.8.2/examples/data/geojson/countries.geojson',
format: new ol.format.GeoJSON(),
wrapX: false
})
});
You also need a helper function to verify which one is the bigger polygon:
function getMaxPoly(polys) {
var polyObj = [];
//now need to find which one is the greater and so label only this
for (var b = 0; b < polys.length; b++) {
polyObj.push({ poly: polys[b], area: polys[b].getArea() });
}
polyObj.sort(function (a, b) { return a.area - b.area });
return polyObj[polyObj.length - 1].poly;
}
What you want to do is not supported by ol3, at least not 'natively'. There are several ways to accomplish what you want but I don't think doing it on the 'client side' is the best approach.
1 - Simple and fast way, server-side
If you have control over your data / server, then I'd manage which label to show from there. You could create a 'label-specific' field that contains a copy of the text you want to show, and for those you don't leave it blank. That would work if you only want the biggest chunk of island to always have the label.
2 - complex and slow way - client-side
On the client side, in your style function, you could loop in each feature and collect those having the same name as the feature attempting to be labelled, then compare their geometry area. Only label the feature if it has no other features of the same name having a bigger area.
This solution could also be implemented on the server-side. You could return an extra field with value 1 if the feature is the one with the largest area among those sharing the same name, and 0 if it doesn't. You would only label features with this field = 1.

cannot add features to WFS layer

I try to load some features from Geoserver to a vector layer in Openlayers 3.9.0.
var url = 'http://localhost:5550/geoserver/mymap/wfs?service=WFS&'+'version=1.0.0&request=GetFeature&typeName=mymap:layer&'+'outputFormat=application/json&maxFeatures=50';
var projection = ol.proj.get('EPSG:3857');
var extent = [2297128.5, 4618333, 2459120.25, 4763120];
var amir = new ol.source.Vector({
format: new ol.format.GeoJSON(),
loader: function (extent) {
$.ajax(url, {type: 'GET'})
.done(loadFeatures)
.fail(function () {alert("error");});
},
strategy: ol.loadingstrategy.bbox
});
function loadFeatures(response) {
formatWFS = new ol.format.WFS();
var features = formatWFS.readFeatures(response);
amir.addFeatures(features);
//-----------OR---------------------
var features = amir.readFeatures(response);
amir.addFeatures(features);
}
var fill = new ol.style.Fill({
color: 'rgba(0,0,0,0.2)'
});
var stroke = new ol.style.Stroke({
color: 'rgba(0,0,0,0.4)'
});
var circle = new ol.style.Circle({
radius: 6,
fill: fill,
stroke: stroke
});
jake = new ol.layer.Vector({
source: amir,
style: new ol.style.Style({
fill: fill,
stroke: stroke,
image: circle
})
});
In loadFeatures function if I use
formatWFS = new ol.format.WFS();
var features = formatWFS.readFeatures(response);
amir.addFeatures(features);
I get Uncaught AssertionError: Failure: Unknown source type pointing to a openlayers line that throws errors and to this line of my codevar features = formatWFS.readFeatures(response);.
If I use
var features = amir.readFeatures(response);
amir.addFeatures(features);
I get Uncaught TypeError: sourceVector.readFeatures is not a function pointing to var features = amir.readFeatures(response); .
The request to the WFS looks ok, with OK 200 status. If I grab the request's URL sended to Geoserver and open it in a new tab I get raw GeoJSON like {"type":"FeatureCollection","totalFeatures":422,"features":[{"type":"Feature","id":"layer.709","geometry":{"type":"Point","coordinates":[2391735.8907621,4695330.8039257005]},"geometry_name":"l_geom","properties":{"l_name":"Leeron"}},....//next feature
So its a FeatureCollection not just an array. Not that I know how to handle this
I dont get why to set a ol.format.WFS and not just read/add features. I dont know how to debug and add the features to my layer
You are instructing GeoServer to use GeoJSON as output format, so you'll need to use the GeoJSON format in OpenLayers to parse the features. You should be able to simplify your source configuration to something like
var url = 'http://localhost:5550/geoserver/mymap/wfs?service=WFS&' +
'version=1.0.0&request=GetFeature&typeName=mymap:layer&' +
'outputFormat=application/json&maxFeatures=50';
var amir = new ol.source.Vector({
format: new ol.format.GeoJSON(),
url: function(extent, projection) {
return url + '&bbox=' + extent.join(',') +
'&srsName=' + projection.getCode();
},
strategy: ol.loadingstrategy.bbox
});

Resources