Openlayers 3: how to select a feature programmatically using ol.interaction.Select? - javascript

I'm using OpenLayers v3.6 (this is important, because most of solutions that I found and would potentialy work are for OpenLayers 2).
I have a table and when I select a row in that table, I would like to highlight/select a corresponding feature on the OpenLayers map. All features are simple polygons (ol.geom.Polygon) in the same vector layer (ol.layer.Vector).
I set up select interaction like this:
// there is a lot of other code here
...
addSelectListener: function() {
this.SelectInteraction = new ol.interaction.Select({
condition: ol.events.condition.singleClick,
layers: function (layer) {
// defines layer from which features are selectable
return layer.get('id') == 'polygons_layer';
},
style: this.Style.Selected
});
// Map = ol.Map
Map.addInteraction(this.SelectInteraction);
this.SelectInteraction.on('select', this.selectPolygon, this);
}
...
selectPolygon: function(event) {
var selectSrc = this.getSelectInfo(event);
// some code that relies on selectSrc data
}
...
getSelectInfo: function (event) {
var selectSrc = {
deselected: null,
selected: null,
type: null
};
if (event.selected.length == 0 && event.deselected.length == 1) {
// click outside of polygon with previously selected
selectSrc.type = 'deselect';
selectSrc.deselected = {
feature: event.deselected[0],
id: event.deselected[0].getId()
};
} else if (event.deselected.length == 0 && event.selected.length == 1) {
// click on polygon without previously selected
selectSrc.type = 'select';
selectSrc.selected = {
feature: event.selected[0],
id: event.selected[0].getId()
}
} else if (event.deselected.length == 0 && event.selected.length == 1) {
// click on polygon with previously selected
selectSrc.type = 'switch';
selectSrc.deselected = {
feature: event.deselected[0],
id: event.deselected[0].getId()
};
selectSrc.selected = {
feature: event.selected[0],
id: event.selected[0].getId()
}
} else {
selectSrc.type = 'out';
}
return selectSrc;
}
This functions well when I want to select polygon by clicking on it on the map. But what I want is to achieve the same, not by clicking on map but rather click on some element outside the map (table row in my example, but it could be anything really).
I would like to use select interaction because of event that is emitted and because of the styling it applies to selected features. However, if by any chance I can just manipulate the selected features in select interaction without having the same event it would be ok.
I'm aware of this question & answer - Openlayers 3: Select a feature programmatically - but the problem is that I cannot ask in comments for clarification (for example, what exactly is mySelectControl), because I don't have any reputation :)

The way to do is in the linked question. So, push a ol.Feature into the selected collection:
var select = new ol.interaction.Select({
//some options
});
map.addInteraction(select);
var selected_collection = select.getFeatures();
selected_collection.push(featurePoint);
If you want to trigger the select event:
select.dispatchEvent('select');
// OR
select.dispatchEvent({
type: 'select',
selected: [featurePoly],
deselected: []
});
See demo!

Related

How to return specific Geojson feature property using another property, e.g. return longitude property by name?

I'm trying to create an interface with Mapbox that zooms to the polygon the user clicks on using this function while also updating a dropdown menu value in an non-map div.
When the user clicks on a polygon/town in the map, it will zoom to that correct feature by using this map.on('click...) script.
map.on('click', 'Map Layer', function (e) {
townnames = e.features[0].properties.Object_Name;
townlat = e.features[0].properties.Object_latitude;
townlong = e.features[0].properties.Object_longitude;
map.setFilter('LayerFiltered', ['==', 'TOWN_NAME', Object_Name]);
map.setFilter('LayerClicked', ['!=', 'TOWN_NAME', Object_Name]);
map.setLayoutProperty('LayerFiltered', 'visibility', 'visible');
map.flyTo({
center: [
townlong, townlat],
zoom : 11.5
});
However I'm trying to trigger the same function using the dropdown menu.
I've figured out how to filter the layer using the inner HTML value from options in the dropdown menu, and setting them equal to the "Object_Name" in setFilter. But is there any way to return other properties from the geojson associated with a feature based on one of its properties like "TOWN_NAME"?
In other words can I use a variable from the dropdown menu so instead of returning e.features[0].properties.Object_longitude when a user clicks on a feature, when they click on its associated value in the dropdown menu it will return something to the effect of-"features.TOWN_NAME=DROPDOWN_VALUE_NAME".properties.Object_longitude"?
I apologize if this is unclear.
The dropdown does not fire the load event. You have to break the work above into general methods to be called by a handler for the map and for the dropdown.
Edit: The drop down is not being assigned the click event and would need to be given a listener to call a function that is also called by the map objects click listener.
demo
// Add an event listener for the links in the sidebar listing
listings.addEventListener('change', function () {
// Update the currentFeature to the store associated with the clicked link
var clickedListing = data.features[listings.selectedIndex];
// 1. Fly to the point associated with the clicked link
flyToStore(clickedListing);
// 2. Close all other popups and display popup for clicked store
createPopUp(clickedListing);
});
}
// Add an event listener for when a user clicks on the map
map.on('click', function (e) {
// Query all the rendered points in the view
var features = map.queryRenderedFeatures(e.point, { layers: ['locations'] });
if (features.length) {
var clickedPoint = features[0];
// 1. Fly to the point
flyToStore(clickedPoint);
// 2. Close all other popups and display popup for clicked store
createPopUp(clickedPoint);
// Find the index of the store.features that corresponds to the clickedPoint that fired the event listener
var selectedFeature = clickedPoint.properties.address;
for (var i = 0; i < stores.features.length; i++) {
if (stores.features[i].properties.address === selectedFeature) {
selectedFeatureIndex = i;
}
}
// Select the correct list item using the found index
var listings = document.getElementById('mySelect').selectedIndex = selectedFeatureIndex ;
}
});

Searching markers with Leaflet.Control.Search from drop down list

I use Leaflet.Control.Search for searching markers by GeoJSON features and it works OK. Now I have to type first letters to find marker, but I want to choose them from drop down list with all markers in it. Is it way to do it?
If you want a dropdown list with all your markers, you are better off creating a custom control rather than trying to modify Leaflet.Control.Search. Creating a control with a select element that contains all your markers is a little complicated, but certainly far simpler than tweaking the code of someone else's finished project.
Start by creating an empty control:
var selector = L.control({
position: 'topright'
});
To put content within the control, you can use the control's .onAdd method. Create a container div for the control using L.DomUtil.create, which in this context will automatically assign the class leaflet-control, allowing any content within the div to display on the map and behave like a control should behave. Then create a select element within the div. Give it a default option if you want. Most importantly, give it an id, so that you can refer to it later:
selector.onAdd = function(map) {
var div = L.DomUtil.create('div', 'mySelector');
div.innerHTML = '<select id="marker_select"><option value="init">(select item)</option></select>';
return div;
};
Now that the control knows what to do when added to the map, go ahead and add it:
selector.addTo(map);
To add all your markers as options in the selector, you can use the .eachLayer method, which iterates through all the markers in the group and calls a function for each. For each layer, create an option element and append it to the select element, using the id assigned above. Assuming that you have created a GeoJSON layer called markerLayer, which has a property called STATION that you want to use as the option text, it would look like this:
markerLayer.eachLayer(function(layer) {
var optionElement = document.createElement("option");
optionElement.innerHTML = layer.feature.properties.STATION;
optionElement.value = layer._leaflet_id;
L.DomUtil.get("marker_select").appendChild(optionElement);
});
Here, we're relying on the fact that each layer gets assigned a unique internal id number, _leaflet_id, when created. We set each option's value attribute to the corresponding layer's _leaflet_id, so that when the option is selected, we have a way to access the marker.
Finally, to get the control to actually do something when you select one of the options, add some event listeners, using the id of the selector element:
var marker_select = L.DomUtil.get("marker_select");
L.DomEvent.addListener(marker_select, 'click', function(e) {
L.DomEvent.stopPropagation(e);
});
L.DomEvent.addListener(marker_select, 'change', changeHandler);
The click listener with the stopPropagation method is to prevent clicks on the selector from propagating through to the map if they overlap with the map pane, which could immediately unselect the layer you are trying to highlight. The change listener will run a handler function, which you can set to do anything you want. Here, I've set it to open the popup for a marker when its corresponding option is selected:
function changeHandler(e) {
if (e.target.value == "init") {
map.closePopup();
} else {
markerLayer.getLayer(e.target.value).openPopup();
}
}
And that's it! Here is an example fiddle with all this working together:
http://jsfiddle.net/nathansnider/ev3kojon/
EDIT:
If you are using the MarkerCluster plugin, you can modify the change handler function to work with clustered markers using the .zoomToShowLayer method:
function changeHandler(e) {
if (e.target.value == "init") {
map.closePopup();
} else {
var selected = markerLayer.getLayer(e.target.value);
markerClusterLayer.zoomToShowLayer(selected, function() {
selected.openPopup();
})
}
}
Example:
http://jsfiddle.net/nathansnider/oqk6u0sL/
(I also updated the original code and example to use the .getLayer method rather than ._layers[e.target.value], because this is a cleaner way to access the marker based on its id)
There would be a way to slightly modify Leaflet-search plugin so that it shows the full list of markers when user clicks on the magnifier button (i.e. when user expands the search control).
As if the search was triggered for 0 typed letter.
Using option minLength: 0 without modifying the plugin code does not trigger a search without typing unfortunately.
L.Control.Search.mergeOptions({
minLength: 0 // Show full list when no text is typed.
});
L.Control.Search.include({
_handleKeypress: function(e) {
switch (e.keyCode) {
case 27: //Esc
this.collapse();
break;
case 13: //Enter
if (this._countertips == 1)
this._handleArrowSelect(1);
this._handleSubmit(); //do search
break;
case 38: //Up
this._handleArrowSelect(-1);
break;
case 40: //Down
this._handleArrowSelect(1);
break;
case 37: //Left
case 39: //Right
case 16: //Shift
case 17: //Ctrl
//case 32://Space
break;
case 8: //backspace
case 46: //delete
this._autoTypeTmp = false;
if (this._collapsing) { // break only if collapsing.
break;
}
default: //All keys
this._doSearch(); // see below
}
},
// externalized actual search process so that we can trigger it after control expansion.
_doSearch: function() {
if (this._input.value.length)
this._cancel.style.display = 'block';
else
this._cancel.style.display = 'none';
if (this._input.value.length >= this.options.minLength) {
var that = this;
clearTimeout(this.timerKeypress);
this.timerKeypress = setTimeout(function() {
that._fillRecordsCache();
}, this.options.delayType);
} else
this._hideTooltip();
},
expand: function(toggle) {
toggle = typeof toggle === 'boolean' ? toggle : true;
this._input.style.display = 'block';
L.DomUtil.addClass(this._container, 'search-exp');
if (toggle !== false) {
this._input.focus();
this._map.on('dragstart click', this.collapse, this);
}
this.fire('search_expanded');
this._doSearch(); // Added to trigger a search when expanding the control.
return this;
},
collapse: function() {
this._hideTooltip();
this._collapsing = true; // added to prevent showing tooltip when collapsing
this.cancel();
this._collapsing = false; // added to prevent showing tooltip when collapsing
this._alert.style.display = 'none';
this._input.blur();
if (this.options.collapsed) {
this._input.style.display = 'none';
this._cancel.style.display = 'none';
L.DomUtil.removeClass(this._container, 'search-exp');
if (this.options.hideMarkerOnCollapse) {
this._markerLoc.hide();
}
this._map.off('dragstart click', this.collapse, this);
}
this.fire('search_collapsed');
return this;
}
});
Include this code in your JavaScript before instantiating L.Control.Search.
Demo: http://jsfiddle.net/ve2huzxw/190/
The big drawback of this solution is that the list of markers is built within the map container. As such, if it is too big, it will be cropped on the bottom, whereas a true select (drop-down) input would "overflow" beyond the container, as in nathansnider's solution.

Search wms layer in Openlayers based on attributes, zoom on it if found

I'm using Openlayers 2.14 and use WMS fron GeoServer, I was able to extract the info from the layer when it is clicked through getfeatureinfo events and using new OpenLayers.Control.WMSGetFeatureInfo. Somewhat like this:
function activateControls(layerName) {
//get wms feature info start
infoControls ={
click: new OpenLayers.Control.WMSGetFeatureInfo({
url: geoserver_url,
title: 'Identify features by clicking',
layers: [layerName],
queryVisible: true,
infoFormat:'application/vnd.ogc.gml',
eventListeners: {
getfeatureinfo: function(event) {
//console.log(event);
//var obj = jQuery.parseJSON(event.text);
//console.log(event.text);
//remove pop-ups when selecting others
var pops = map.popups;
for (var a = 0; a < pops.length; a++) {
if (pops.length == 1) {
map.removePopup(map.popups[0]);
}
};
map.addPopup(new OpenLayers.Popup.FramedCloud(
"chicken",
map.getLonLatFromPixel(event.xy),
null,
GenPopText(event),
null,
true
));
}
}
})
};
for (var i in infoControls) {
infoControls[i].events.register("getfeatureinfo", this, showInfo);
map.addControl(infoControls[i]);
}
infoControls.click.activate();
}//end of get wms feature info
function showInfo(evt) {
if (evt.features && evt.features.length) {
highlightLayer.destroyFeatures();
highlightLayer.addFeatures(evt.features);
highlightLayer.redraw();
//console.log(GenPopText(evt));
} else {
console.log(evt.text);
}
}
function GenPopText(evt){
var temstr= "<b><i>" + evt.features[0].gml.featureType + "</i></b><br/>";
for(var key in evt.features[0].attributes){
temstr += "<b><span class='text-capitalize'>" + key + "</span></b>:" + evt.features[0].attributes[key] + "<br/>";
}
return temstr
}
I created a function for it because I have several WMS layer.
Now, as what the question implied. I wanted to search the layer based on attributes like its building name as basis and show the pop-up when found and zoom on it.
This is how I want to implement it:
$("#table_brgy").on("click", "tbody tr", function (e) {
e.preventDefault();
var building_name = $(this).find("td").first().text();
....
activateControls(layerName,building_name)
});
Like this:
When the table row is clicked it will show the matching building info in pop-up.
I have done my research but I can't seem to make it work:
LINK 1
The OGC WMS Standard does NOT support queries based on attributes, it only supports them based on points (possible operations here). What you need is a WFS Service and it's GetFeature operation.
Sample code: http://dev.openlayers.org/examples/wfs-states.html

click function to only execute on certain data names

I have this click:function for an interactive jQuery US map.
The below defines the "active color states" -- the color states turn after being clicked. I would like to alter to only work / execute on a few states I choose. Every state has a data.name with it's abbreviation. Eg. 'OH' 'CA' 'TX" 'NY' etc. The active color removes upon click another states (as is below) I just need the ability to have this work with ONLY my select chosen states, not all 50.
click: function(c, l) {
$("#map > svg > path").each(function() {
$(this).css("fill", "")
}),
$("#" + l.name).css("fill", "#ffc600"),
Define an array with allowed states var clickable = ['OH','CA','TX','NY']; and then use indexOf to see if the clicked state is in it..
Try
click: function(c,l){
var node = $(l.shape.node),
siblings = node.siblings('path');
siblings.css('fill','');
if (clickable.indexOf(l.name) > -1){
// select it
node.css('fill','green');
}
}
Demo at http://codepen.io/gpetrioli/pen/dPLGVg
You could do the styling with CSS, though
(example requires browsers the support the classList property)
click: function(c,l){
var active = $(l.shape.node)
.siblings('.selected')
.get(0);
// if there is an selected path
if (active) {
// de-select it
active.classList.remove('selected');
}
// if clicked node is in list of clickables
if (clickable.indexOf(l.name) > -1){
// select it
l.shape.node.classList.add('selected')
}
}
Demo at http://codepen.io/gpetrioli/pen/dPLGRg
How about using the configuration options of the USMap and using its stateSpecificStyles
$('#map').usmap({
stateSpecificStyles: {
'MD': {fill: '#ffc600'},
'VA': {fill: '#ffc600'}
}
});
You could then collect all clicked states and update the stateSpecificStyles accordingly:
var clickedStates = []; // array with states that have been clicked
click: function(c, l) {
// if state was clicked before no need for update - so check
if (clickedStates.indexOf(l.name) < 0) {
// remember clicked state
clickedStats.push(l.name);
// build new state styles
var styles = {};
for (var i=clickedStats.length-1; i >= 0; i--) {
styles[clickedStats[i]] = {fill: '#ffc600'};
}
// pass new style object to map
$('#map').usmap({
stateSpecificStyles: styles
});
}
}
That should change the color than of each map clicked and if you want to implement a different logic (clicking CA colors CA, NY and TX) then you simply add to the clickedStates array.
Hope that helped

Kendo UI Web - MultiSelect: select an option more than once

I'm currently facing a problem with the Kendo UI MultiSelect widget for selecting an option more than once. For example, in the image below I want to select Schindler's List again after selecting The Dark Knight, but unfortunately the MultiSelect widget behaves more like a set than an ordered list, i.e. repetitive selection is not allowed. Is there actually a proper way to achieve this? Any workarounds?
That's the intended behavior of the multi-select control and there is no simple way to make it do what you want using the available configuration options. Possible workarounds are ...
Creating a custom multi-select widget
Something like this should work (note that I haven't tested this much - it lets you add multiples and keeps the filter intact though):
(function ($) {
var ui = kendo.ui,
MultiSelect = ui.MultiSelect;
var originalRender = MultiSelect.fn._render;
var originalSelected = MultiSelect.fn._selected;
var CustomMultiSelect = MultiSelect.extend({
init: function (element, options) {
var that = this;
MultiSelect.fn.init.call(that, element, options);
},
options: {
name: 'CustomMultiSelect'
},
_select: function (li) {
var that = this,
values = that._values,
dataItem,
idx;
if (!that._allowSelection()) {
return;
}
if (!isNaN(li)) {
idx = li;
} else {
idx = li.data("idx");
}
that.element[0].children[idx].selected = true;
dataItem = that.dataSource.view()[idx];
that.tagList.append(that.tagTemplate(dataItem));
that._dataItems.push(dataItem);
values.push(that._dataValue(dataItem));
that.currentTag(null);
that._placeholder();
if (that._state === "filter") {
that._state = "accept";
}
console.log(this.dataSource.view());
},
_render: function (data) {
// swapping out this._selected keeps filtering functional
this._selected = dummy;
return originalRender.call(this, data);
this._selected = originalSelected;
}
});
function dummy() { return null; }
ui.plugin(CustomMultiSelect);
})(jQuery);
Demo here.
Using a dropdown list
Use a simple dropdown list (or ComboBox) and bind the select event to append to your list (which you have to create manually).
For example:
var mySelectedList = [];
$("#dropdownlist").kendoDropDownList({
select: function (e) {
var item = e.item;
var text = item.text();
// store your selected item in the list
mySelectedList.push({
text: text
});
// update the displayed list
$("#myOrderedList").append("<li>" + text + "</li>");
}
});
Then you could bind clicks on those list elements to remove elements from the list. The disadvantage of that is that it requires more work to make it look "pretty" (you have to create and combine your own HTML, css, images etc.).

Categories

Resources