How to show one label per multi-polygon in open layers 3? - openlayers-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.

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
}
}

How to change style in geojson vector layer with multipolygons

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);
});

update position of geolocation marker in Openlayers 3

I wan't to have a marker on the map, which position is dynamically updated.
When I create the layer inside the function of the geolocation.on('change'-event it works, but the layer is added each time the geolocation changes. Therefore I wanted to create the layer outside that function and update only the position of the marker.
With the folowing code I get an 'TypeError: a is null'
var geolocation = new ol.Geolocation({
projection: map.getView().getProjection(),
tracking: true,
trackingOptions: {
enableHighAccuracy: true,
maximumAge: 2000
}
});
var iconStyle = new ol.style.Style({
image: new ol.style.Icon({
anchor: [0.5, 46],
anchorXUnits: 'fraction',
anchorYUnits: 'pixels',
opacity: 0.75,
src: './_img/marker_.png'
})
});
var pos1 = geolocation.getPosition();
var iconFeature = new ol.Feature({
geometry: new ol.geom.Point(pos1)
});
var iconSource = new ol.source.Vector({
features: [iconFeature]
});
var iconLayer = new ol.layer.Vector({
source: iconSource,
style : iconStyle
});
map.addLayer(iconLayer);
geolocation.on('change', function() {
var pos_upd = geolocation.getPosition();
iconFeature.getGeometry().setCoordinates(pos_upd);
view.setCenter(pos_upd);
view.setZoom(18);
});
The browser Geolocation API, which is wrapped by ol.Geolocation, is asynchronous. After initiation, geolocation.getPosition() will always return undefined until the first change event.
The proper thing to do is to add the feature once you get a coordinate, in the change event handler.
You will need to use a conditional to determine if you should add ore update a feature.
I changed the code a bit, so that I created first an iconFeature, that wasn't already bound to a ol.geom.Point()-position. By this way there was no need to use geolocation.getPosition().
Later in the geolocation.on('change')-event I assigned the actual position to the geometry of the iconFeature.
Works like espected
// add an empty iconFeature to the source of the layer
var iconFeature = new ol.Feature();
var iconSource = new ol.source.Vector({
features: [iconFeature]
});
var iconLayer = new ol.layer.Vector({
source: iconSource,
style : iconStyle
});
map.addLayer(iconLayer);
// Update the position of the marker dynamically and zoom to position
geolocation.on('change', function() {
var pos = geolocation.getPosition();
iconFeature.setGeometry(new ol.geom.Point(pos));
view.setCenter(pos);
view.setZoom(18);
});

Openlayers 3: Drawing grid lines (graticule) with predefined units on the custom static image

I am trying to draw custom x-y axes grid lines on top of a static image, i.e. image pixels rather than lattitude and longitudes. Ideally, the grid lines should be redrawn dynamically when I drag/zoom/scroll the image, just like the x-y ruler bars in Photoshop.
I came across the following code example, which provides a custom projection function to directly map image pixel coordinates to map coordinates.
http://openlayers.org/en/latest/examples/static-image.html
// Map views always need a projection. Here we just want to map image
// coordinates directly to map coordinates, so we create a projection that uses
// the image extent in pixels.
var extent = [0, 0, 1024, 968];
var projection = new ol.proj.Projection({
code: 'xkcd-image',
units: 'pixels',
extent: extent
});
I tried to append the following code to the script. However, the ol.Graticule class seems to be incompatible with the custom ol.proj.Projection definition.
http://openlayers.org/en/latest/examples/graticule.html
// Create the graticule component
var graticule = new ol.Graticule({
// the style to use for the lines, optional.
strokeStyle: new ol.style.Stroke({
color: 'rgba(255,120,0,0.9)',
width: 2,
lineDash: [0.5, 4]
})
});
graticule.setMap(map);
What's wrong with the above code?
P.S. I am aware of the Openseadragon API which provides a dynamic scalebar. However, I wish to stick to Openlayers API because I also have an extra map layer of anchor points at predefined locations on the static image.
I had the same problem. For this to work I created a Vector Layer, (where axis are drawn).
To draw the axis, I need to listen to View changes.
Whenever the view changes, calculate the actual extent for the view.
With extent information and ([width, height] of the image, you can then draw axis)
let listenerAxis = null,
w = 0,
h = 0
const xAxisStyle = new ol.style.Style({
stroke: new ol.style.Stroke({
color: 'red',
width: 2
})
})
const yAxisStyle = new ol.style.Style({
stroke: new ol.style.Stroke({
color: 'green',
width: 2
})
})
const ImageLayer = new ol.layer.Image()
const AxisLayer = new ol.layer.Vector({ source: new ol.source.Vector() })
AxisLayer.setStyle((feature, resolution) => {
if(feature.getProperties().axis == 'x') {
return xAxisStyle
}
return yAxisStyle
})
const renderer = new ol.Map({
target: 'map',
layers: [ImageLayer]
})
AxisLayer.setMap(renderer)
processFile('https://i2.wp.com/beebom.com/wp-content/uploads/2016/01/Reverse-Image-Search-Engines-Apps-And-Its-Uses-2016.jpg?resize=640%2C426')
function removeAxis() {
AxisLayer.getSource().clear()
ol.Observable.unByKey(listenerAxis)
listenerAxis = null
}
function drawAxis() {
function draw(){
AxisLayer.getSource().clear()
const extent = renderer.getView().calculateExtent()
const [xmin, ymin, xmax, ymax] = extent
// Eje X
const axisX = new ol.geom.LineString([ [xmin, h / 2], [xmax, h / 2] ])
const axisY = new ol.geom.LineString([ [w / 2, ymin], [w / 2, ymax] ])
const featureX = new ol.Feature({ geometry: axisX, axis: 'x' })
const featureY = new ol.Feature({ geometry: axisY, axis: 'y' })
AxisLayer.getSource().addFeatures([featureX, featureY])
}
listenerAxis = renderer.getView().on('change', draw)
draw()
}
async function processFile(path) {
ImageLayer.setSource()
removeAxis()
if(!path) {
return
}
const [wi, hi] = await readImage(path)
w = wi
h = hi
const source = getImageStatic(path, w, h)
const view = getViewForImage(w, h)
ImageLayer.setSource(source)
renderer.setView(view)
drawAxis()
}
// Some helpers
function readImage(localPath) {
const img = document.createElement('img')
return new Promise((res, rej) => {
img.src = localPath
img.addEventListener('load', (event) => {
const { naturalWidth, naturalHeight } = img
console.log('img', naturalWidth, naturalHeight)
res([naturalWidth, naturalHeight])
})
})
}
function getViewForImage(w, h) {
return new ol.View({
center: [w / 2, h / 2],
zoom: 2,
projection: new ol.proj.Projection({
extent: [0, 0, w, h],
units: 'pixels'
}),
extent: [0, 0, w, h]
})
}
function getImageStatic(path, w, h) {
return new ol.source.ImageStatic({
url: path,
imageExtent: [0, 0, w, h]
})
}
#map {
width: 100%;
height: 100%;
background: grey;
}
<link href="https://openlayers.org/en/v4.6.5/css/ol.css" rel="stylesheet"/>
<script src="https://openlayers.org/en/v4.6.5/build/ol.js"></script>
<div id="map"></div>

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