Cannot get drawn polygons and lines in openlayers 3 vector layer - openlayers-3

I wrote a code that supposed to get the feature that is just created on a vector layer.
<select id="type">
<option value="Point">Point</option>
<option value="LineString">LineString</option>
<option value="Polygon">Polygon</option>
</select>
//is actually empty
var sourceVector = new ol.source.Vector({});
layerVector = new ol.layer.Vector({
id: 'myLayer',
source: sourceVector,
style:cStyle // I set a style, I just dont iclude it for brevity
});
var draw;
var typeSelect = document.getElementById('type');
function addInteraction() {
draw = new ol.interaction.Draw({
source: sourceVector,
type: typeSelect.value
});
map.addInteraction(draw);
}
//when something is selected by the dropdown
//remove the previous interaction and reset it
typeSelect.onchange = function(e) {
map.removeInteraction(draw);
addInteraction();
};
addInteraction();
//when drawing is finished, get coords and print them
draw.on('drawend',
function(evt) {
var justNowFeature = evt.feature;
var featureGeom = justNowFeature.getGeometry().getCoordinates();
console.log("FEATURESGEOJSON "+featureGeom );
}
);
This also works for getting the geometry type
var featureGeom = justNowFeature.getGeometry().getType();
And actually all I want to save is the type and the coordinates, so I am fine.
Except, this only works for Points. If I choose Polygon or LineString, it does not print anything in the console, and I dont get any errors, it just not work.
Any solutions?
Thank you

you need to place 'drawend' event initilisation within the addInteraction function. Check the fiddle here. fiddle here
When initialising 'drawend' event no draw interaction exist. Firebug though should inform you that "draw" is undefined
this is your code. I have made some comments in CAPITALS to explain.
<select id="type">
<option value="Point">Point</option>
<option value="LineString">LineString</option>
<option value="Polygon">Polygon</option>
</select>
//is actually empty
var sourceVector = new ol.source.Vector({});
layerVector = new ol.layer.Vector({
id: 'myLayer',
source: sourceVector,
style:cStyle // I set a style, I just dont iclude it for brevity
});
var draw;
var typeSelect = document.getElementById('type');
function addInteraction() {
draw = new ol.interaction.Draw({
source: sourceVector,
type: typeSelect.value
});
map.addInteraction(draw);
}
//when something is selected by the dropdown
//remove the previous interaction and reset it
typeSelect.onchange = function(e) {
//HERE YOU REMOVE THE INTERCATION AND THUS ALL LISTENERS
//ASIGNED TO IT REMOVED
// SO YOU NEED TO REASIGN THE LISTENERS AS THIS IS A BRAND NEW INTERACTION
map.removeInteraction(draw);
addInteraction();
};
addInteraction();
//when drawing is finished, get coords and print them
//HERE YOU JUST ASIGN THE LISTENER AT STARTUP.
//THIS HAPPENS ONCE AND NOT EACH TIME YOU
// ADD BACK THE INTERACTION
draw.on('drawend',
function(evt) {
var justNowFeature = evt.feature;
var featureGeom = justNowFeature.getGeometry().getCoordinates();
console.log("FEATURESGEOJSON "+featureGeom );
}
);

Related

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

Uncaught TypeError: Cannot read property 'Input' of undefined

I changed from OpenLayers v3.0.0 to 3.19.1 and now following line doesn't work:
var visible = new ol.dom.Input(document.getElementById('visible'));
Switching back to the older version, everything is ok. What's going wrong?
ol.dom.Input was removed in 3.5.0
The experimental ol.dom.Input component has been removed. If you need to synchronize the state of a dom Input element with an ol.Object, this can be accomplished using listeners for change events. For example, you might bind the state of a checkbox type input with a layer's visibility like this:
var layer = new ol.layer.Tile();
var checkbox = document.querySelector('#checkbox');
checkbox.addEventListener('change', function() {
var checked = this.checked;
if (checked !== layer.getVisible()) {
layer.setVisible(checked);
}
});
layer.on('change:visible', function() {
var visible = this.getVisible();
if (visible !== checkbox.checked) {
checkbox.checked = visible;
}
});

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.

Can't access KML features

I am loading a KML file locally and I have been able to add it to the map successfully. However, I want to interate over the features and can't seem to get anything to work. My code currently:
var myLayer = new ol.layer.Vector({
source: new ol.source.Vector({
url: '/kml/sample.kml',
format: new ol.format.KML()
})
});
// Iterate over features *NOT WORKING*
myLayer.getSource().forEachFeature(function(e) {
console.log(e);
})
Any pointers on how I can get the forEachFeature to function, or any alternative method, would be great.
The code in your question works fine, except that the features are loaded asynchronously. Most of the time it will first execute forEachFeature, which finds 0 features to loop through and afterwards the features are loaded.
You may find out that a single feature is loaded by listening for the addfeature event of the source and maybe you can make your desired changes there for each feature separately:
var id = 1;
myLayer.getSource().on('addfeature', function (ev_add) {
console.log(ev_add.feature);
ev_add.feature.once('change', function (ev_change) {
console.log(ev_change.target.getId());
});
ev_add.feature.setId(x);
x += 1;
});
If you must wait until all features are loaded, the change event of the layer can help:
myLayer.once('change', function () {
myLayer.getSource().forEachFeature(function (feature) {
console.log(feature);
});
});
Edit: You are right, the addfeature event handler has the event object as parameter. To your question about setting the ID while adding features, I think that this is again a problem of waiting until the changes are done. I made the amendments in the first snippet.
I found a way to get this to work. Not sure if it's the most efficient however:
var featProj = map.getView().getProjection();
var kmlFormat = new ol.format.KML();
var myLayer = new ol.layer.Vector();
var vectorSource = new ol.source.Vector({
loader: function() {
$.ajax( {
url: '/kml/my.kml',
success: function( data ) {
var features = kmlFormat.readFeatures( data, { featureProjection: featProj } );
vectorSource.addFeatures( features );
// iterate over features
vectorSource.forEachFeature( function( feature ) {
//do something
console.log( feature );
});
}
});
},
strategy: ol.loadingstrategy.bbox
});
myLayer.setSource( vectorSource );

Angular view gets out of sync with model when dragging items from one list to another

I have created a custom directive that allows me to connect multiple sortable lists via drag and drop using angular js and jquery ui. The way it should work is the following:
When drag starts, keep track of the initial position of the item in the array and the value of ng-model for that sortable
When the drag ends, if the item is received to a different list, keep track of the ng-model of that list and the target position of the element
Broadcast an event with that data so that the controller can change the positions of the items from one array to another
The problem is that once I move one item from one list to another, even though the items in the arrays go where they should, in the view some HTML elements disappear.
Here is the sortable directive:
app.directive('mySortable',function(){
return {
link:function(scope,el,attrs){
var options = {};
if(attrs.connectWith)
{
options.connectWith = attrs.connectWith;
}
el.sortable(options);
el.disableSelection();
el.on("sortstart", function(event, ui){
var from_index = angular.element(ui.item).scope()?angular.element(ui.item).scope().$index : 0;
var from_model = angular.element(ui.item.parent()).attr('ng-model');
ui.item.scope().sortableData = {from_index: from_index, from_model: from_model};
});
el.on("sortreceive", function(event, ui){
ui.item.scope().sortableData.to_index = el.children().index(ui.item);
ui.item.scope().sortableData.to_model = angular.element(el).attr('ng-model');
});
el.on( "sortdeactivate", function( event, ui ) {
var to_model = angular.element(el).attr('ng-model');
var from = angular.element(ui.item).scope()?angular.element(ui.item).scope().$index : 0;
var to = el.children().index(ui.item);
if(to>=0){
scope.$apply(function(){
if(from>=0){
scope.$emit('list-sorted', {from:from,to:to}, ui.item.scope());
}else{
scope.$emit('list-appended', {to:to, name:ui.item.text()});
ui.item.remove();
}
})
}
} );
}
}
})
And here is the controller logic that handles it's event:
$scope.$on('list-sorted', function(ev, val, task_scope){
var sd = task_scope.sortableData;
if(sd.to_model)
{
$timeout(function(){
$scope[sd.to_model].splice(sd.to_index, 0, $scope[sd.from_model].splice(sd.from_index, 1)[0]);
});
}
else
{
$timeout(function(){
$scope[sd.from_model].splice(val.to, 0, $scope[sd.from_model].splice(val.from, 1)[0]);
});
}
console.log($scope);
});
What's wrong?
Example JS Fiddle
It seems that the controller logic comports an error.
Is it fine like this:
var sd = item_scope.sortableData;
// If the item is supposed to be dropped to a different list, move it from one list to another
if(sd.to_model)
{
console.log("to a different list", val)
$timeout(function(){
$scope[sd.to_model].splice(val.to, 0, $scope[sd.from_model].splice(sd.from_index, 0));
});
}
else
{
console.log("to the same list")
$timeout(function(){
$scope[sd.from_model].splice(val.to, 0, $scope[sd.from_model].splice(val.from, 1)[0]);
});
}

Resources