Leaflet - access specific polyline feature (GeoJSON)? - javascript

The situation: My web application shows a map with different trails of interest (my so called POIs) and a sidebar with information about each POI. Selecting a panel of the sidebar, the related POI should be selected/highlighted on the map.
Data and platforms used: I work with Leaflet and JavaScript, no jQuery. The data are added within Leaflet as GeoJSON. The trails are represented as polylines, but I call them POIs (just to clarify). I do not and cannot use jQuery.
What works: The trails (polylines) are added like this:
var pois = L.geoJson(pois,
{
style: style_pois,
onEachFeature: onEachFeature
});
pois.addTo(map);
function onEachFeature(feature, layer)
{
layer.on('click', function (e)
{
sidebar.open('pois');
//get the ID of the clicked POI
number = feature.properties.number;
//Open the accordion tab
var panel = document.getElementsByClassName('accordion-panel');
panel[number-1].style.display="block";
//highlight the selected POI
var selectedFeature = e.target;
selectedFeature.setStyle(style_pois_selected);
});
}
What does not work: Selecting a panel of the accordion, I get the ID of the related trail (polyline), but I cannot access and highlight this certain polyline feature within Leaflet.
This is the JavaScript code, where the accordion behavior is controlled:
var acc = document.getElementsByClassName('accordion');
var panel = document.getElementsByClassName('accordion-panel');
for (var i = 0; i < acc.length; i++)
{
(function(index){
acc[i].onclick = function()
{
// Toggle between adding and removing the "active" class,
//to highlight the button that controls the panel
this.classList.toggle("active");
//Toggle between hiding and showing the active panel
var panel = this.nextElementSibling;
console.log("panel " + acc[0]);
if (panel.style.display === "block") {
panel.style.display = "none";
} else {
panel.style.display = "block";
}
var myIndex = index + 1;
alert("INDEX " + myIndex);
}
})(i);
}
Question: Is there a possibility, based on a layer that is included as GeoJSON in Leaflet to access a certain feature based on any property?
What I tried: I only came across solutions where the different behavior of a certain polyline is accessed within the onclick function. There it is easily possible to apply another color (setStyle). I need to access it from outside the layer. I already tried to again load the pois layer as I did above, just inside the accordion JavaScript and filter it for the certain ID so that only the one polyline is represented, but it only gave me an error that it is an invalid GeoJSON object (maybe a scope issue?).
I appreciate any help!

For anyone who might come across the same problem - I found a solution.
I looked for hours to find out, if one can access a specific feature from a GeoJSON layer within Leaflet, but it seemed that there is no such method.
Although there is no official method for it, for me worked the following.
When inside the accordion, one can just access the already loaded GeoJSON dataset, in my case pois and get the layer (this actually gets the feature, not the layer! a bit misleading) at the index position. For this one, a style can then be applied.
pois.getLayer(index).setStyle(style_pois)
To read out the index of the clicked accordion panel, I asked another question and was pointed in the right direction: Simple JavaScript accordion - how to get the index of the clicked panel?

NOTE: I'd recommend you to set some JFiddle to reproduce your problem.
A solution I often use is to set the ID/Class property in each of the markers/points:
$.getJSON("data/displacement.geojson", function(data){
pathsLayer = L.geoJson(data,{
className: function(feature){ //Sets the class on element
//Assuming your JSON has a property called ID
return "ID-" + feature.properties.ID;
},
style: function (feature) {
//If needed, you can also set style based on properties
},
})
});
After that you can set a global variable to keep record of the selection ID, and then use it to select and modify the specific element. Since Leaflet uses SVG elements, I recommend you to use D3.js to select/modify elements, for instance:
var selectedID = null; //Declare global variable
// You modify selectedID by actions on sidebar, e.g.:
selectedID = 001
d3.select(".ID-" + selectedID)
.transition() //You can set easily transitions on attribute changes
.duration(1000) // in ms
.attr("attributeName", "attributeValue");
You can find an example here (although I know is a bit tricky to read using View Page Source (Ctrl + U))

Related

Capturing an ID hidden inside Leaflet popup on click requires two clicks to update [Javascript]

I have a really annoying issue with javascript in my Django project. Currently building a webbapp which reads data from sensors placed in manholes for water-temperature measurements. We display these sensors as markers on a Leafletmap with the pipe-system between each manhole.
I'm currently storing the sensor-id as a hidden variable in each manhole and then grabbing these to build a D3 graph displaying the temperature data for the specific manhole that has been clicked.
onEachFeature: (feature, layer) => {
for (let i = 0; i < place.length; i++) {
if (place[i].fields.pnamn === feature.properties.pnamn) {
sensorid = place[i].fields.eui;
}
}
var popupText = "<strong>" + feature.properties.pnamn + "<p id='popupText' style='display:none'>" + sensorid + "</p>" + "</strong>";
layer.bindPopup(popupText);
},
[......] }).on('click', onClick).on('popupclose', startZoomer).addTo(map);
The id in question is the sensorid in the p-element. It works as it should, except for the extremely annoying fact that you can just click on a new manhole to update the graph without clicking twice on the new one or by clicking anywhere on the map.
I capture the sensorid in the function below and this is where I believe the problem is hiding. Can't really wrap my head around why this is happening and would appreciate any help at this point in time!
function onClick() {
let id = document.getElementById("popupText").innerText;
urlen = urlen.replace(/([A-Z])\w+/, id);
console.log(id);
console.log(urlen);
var x = document.getElementById("chart-area");
if (x.style.display === "none") {
x.style.display = "block";
} else {
x.style.display = "block";
}
map.scrollWheelZoom.disable();
update();
}
Where the building of the new id for updating the graph is happening is the first 4 rows under the function initialization. The rest is for locking the map for mousewheel scroll when a popup is open so that user can scroll between the graph and map as they are stacked on top of eachother.
It's as I said extremely annoying for me and unacceptable when the system's put to use to have to click twice, and if you don't know this happens it can skew your view as it does update if you just click between manholes but you get the manhole you clicked before the current.
Please help me.
When the click event happens, most probably your new popup is not opened yet. This would explain why you find no element with matching id, or get the previous one.
You might have more luck using the "popupopen" event instead.
But in the first place, relying on scraping your sensor id from the popup text is a strange design, when you have control over the page code.
A more appropriate design would directly associate the sensor id with the Marker or its associated Feature, so that you can retrieve it easily on a Marker click event, instead of having to go through DOM querying.
For example in your onEachFeature:
feature.properties.sensorid = sensorid
Then in your onClick listener:
function onClick(event) {
// the clicked layer is event.layer if the listener is on a Feature Group,
// but is event.target if the listener is directly on that layer.
const sensorid = event.layer.feature.properties.sensorid;
}

Adding a class while looping to an array

I have a list composed by some divs, all of them have a info link with the class .lnkInfo. When clicked it should trigger a function that adds the class show to another div (like some sort of PopUp) so it is visible and when clicked again it should hide it.
I am quite certain this must be a very basic thing and most likely I will get some scoffs...but hey! Once I have this down that's one thing less I will ever have to ask again. Anyway I am starting to leave the safety of html and css to start learning JS, PHP and the like and I came to a bit of a problem.
When testing it before it was working, that was until I added another div, it only worked with the first one, reading a bit and with some suggestion I realized it must be something related to a array, the problem is that I am not quite certain of the syntax for accomplishing what I am visualizing.
Any help would be deeply appreciated.
This is my JS code and below I will attack a Fiddle of how the html looks just in case.
var infoLab = document.getElementsByClassName('lnkInfo'),
closeInfo = document.getElementById('btnCerrar');
infoLab.addEventListener('click', function () {
for (var i = 0 ; i < infoLab.length; i++) {
var links = infoLab[i];
displayPopUp('popUpCorrecto1', 'infoLab[i]');
};
});
function displayPopUp(pIdDiv, infoLab[i]){
var display = document.getElementById(pIdDiv),
for (var i = 0 ; i < infoLab.length; i++) {
infoLab[i]
newClass ='';
newClass = display.className.replace('hide','');
display.className = newClass + ' show';
};
}
JSFiddle.
Thanks a lot in advance and sorry for any facepalms!
EDIT:
This a jQuery function (in another file) that I need to call using the link because it fetches the data that will be inside the div, thus why I wanted to just add a hide/show.
$(".lnkInfo").click(function() {
var id = $('#txtId').val();
var request = $.ajax({
url: "includes/functionsLabs.php",
type: "post",
data: {
'call': 'displayInfoLabs',
'pId':id},
dataType: 'html',
success: function(response){
$('#info').html(response);
}
});
});
EDIT 2:
To a future reader of this question,
If you managed to find this answer throughout space and time, know that this is how the solution ended being, may it help you in your quest to stop being a noob.
SOLUTION
Here is a rudimentary working example of how to make a popup appear after clicking on a specific element given your current code. Note that I added an id to your link element.
// Select the element.
var infoLink1 = document.getElementById('infoLink1');
// Add an event listener to that element.
infoLink1.addEventListener('click', function () {
displayPopUp('popUpCorrecto1');
});
// Display a the popup by removing it's default "hide"
// class and adding a "show" class.
function displayPopUp(pIdDiv) {
var display = document.getElementById(pIdDiv);
var newClass = display.className.replace('hide', '');
display.className = newClass + ' show';
}
Fiddle.
There are various ways to generalize this to work for all links/popups. You could add a data-link-number=1, data-link-number=2, etc to each link element (more on data-). Select an element containing all of your links. Bind to that element an event listener that, when clicked, detects the link element that was clicked (see event delegation / "bubbling"). You can determine which link was clicked based on the value of your data-link-number attribute. Then show the appropriate popup.
You may also want to use jQuery for this. Changing an element's class by setting it's className property makes for brittle DOM code. There is an addClass and a removeClass method available. jQuery's events also work cross-browser; element.addEventListener() will not work in IE8 which still has a significant market share.

JavaScript programatically hover mouse over element

I'm writing a vb.net program to automate and manage an online game. I'm using the Awesomium webcontrols to display and manipulate the pages of the game.
There is a point where I need to grab the data that's not shown in the source until the user hovers over a certain element, how can I use javascript (Not jquery please) to hover over it programatically until the data I need becomes available and then grabbed?
I apologise if this has been asked before (Which it has but from the perspective of someone who owns the web page) but I have been searching for hours for a solution and cant find anything.
What I've tried to use but failed is:
function findBpDate(){
document.getElementById('tileDetails').children[1].children[0].children[1].children[0].fireEvent('onmouseover');
return document.getElementsByClassName('text elementText')[0].textContent;
}
This returns "undefined" when it calls back to my application, I'm certain I'm pointing to the right DOM elements though.
This is what I want the javascript to "hover" on:
<span class="a arrow disabled">Send troops</span>
Once this element has been "hovered" on, this elements text changes to the text I need to grab:
<div class="text elementText">Beginners protection until 20/07/13 07:51 am.</div>
I've shown above what the element looks like when the mouse "hovers" on the element I need it to, however this changes a lot depending on which element the user hovers over while playing the game, from what i gather it's where the source keeps the text for each tooltip in the game.
So I need a function that will hover over a certain element and then while it's hovering, grab the text from the tooltip text/"text elementText" element.
Try WebView.InjectMouseMove(x, y).
Something like
public Point GetElementPosition(dynamic element)
{
dynamic rect = element.getBoundingClientRect();
using (rect)
{
return new Point(rect.left, rect.top);
}
}
dynamic element = webView.ExecuteJavascriptWithResult("document.getElementById('id')");
Point pos = GetElementPosition(element);
webView.InjectMouseMove(pos.X, pos.Y);
this is 10x easier with js/dom. http://jsfiddle.net/pA2Vd/
Do this...assuming you can get reference to elements somehow using by Id would have been lot easier.
var elm = document.getElementsByClassName('a arrow disabled')[0];
var txt = document.getElementsByClassName('text elementText')[0];
var evt = new Event('mouseover');
elm.dispatchEvent(evt);
var status = txt.innerText;
(helpfuL stuff down) otherwise you need to capture event, detect who fired it, check if that has this class and tag name. Lot of processing.
var txt,spn,status='';
document.getElementByTagName('span').forEach(function(d){
if (d.tagName=="div" && d.className == 'text elementText'){
var txt = d;
}
}
window.onmouseover = function(e) {
var elm = e.target;
if (elm.tagName=="SPAN" && elm.className == 'a arrow disabled') {
status=txt.innerText;
}
}

OpenLayers: Unable to close popup when added using call from outside map

I have written a basic function to allow me to display a popup from a link outside the map. The functionality to open the popup is working fine, but I can't then close it.
Demo link: http://www.catchingtherain.com/bikestats/stations.php - click on links in left-hand tabbed panels.
Here's a bit more detail ...
A typical map has about 300 features on a vector layer 'stations' loaded from kml. These are activated onload using
select = new OpenLayers.Control.SelectFeature(stations);
stations.events.on({
"featureselected": onFeatureSelect,
"featureunselected": onFeatureUnselect
});
map.addLayer(stations);
map.addControl(select);
select.activate();
which works fine - I can open and close popups.
With my off-map links I am calling onclick="showMyPopup([x]) with [x] being an ID attribute loaded in from the kml. The showMyPopup function is
function showMyPopup(myID){
for(var a = 0; a < stations.features.length; a++){ //loop through all the features
var feature = stations.features[a];
if (feature.attributes.ID.value == myID) { //until it finds the one with the matching ID attribute
var content = "<h4>" + feature.attributes.name + "</h4>" + feature.attributes.description;
popup = new OpenLayers.Popup.FramedCloud("chicken",
feature.geometry.getBounds().getCenterLonLat(),
new OpenLayers.Size(200,200),
content,
null, true, onPopupClose);
feature.popup = popup;
map.addPopup(popup);
}
}
}
This opens the correct popup from the stations layer as expected, and I can see the popup using the DOM inspector on the stations layer just as it would appear if loaded by clicking on the map feature, but there's then seemingly no way of closing it. The original features on the stations layer are working fine though (opening and closing).
Any help would be much appreciated (maybe there's a simpler way of tackling this?)
Thanks, James
PS and just in case, here's the onFeatureUnselect function ...
function onFeatureUnselect(event) {
var feature = event.feature;
if(feature.popup) {
map.removePopup(feature.popup);
feature.popup.destroy();
delete feature.popup;
}
}
Your on onPopupClose() function is:
function onPopupClose(evt) {
select.unselectAll();
}
When you select feature from map and click on popup's Close icon, then feature will be unselected, but popup is not closed yet. Then, onFeatureUnselect event is triggered, and popup is actually closed.
When you create popup by showMyPopup() function, you are not selecting it. onPopupClose() is called, but it doesn't close popup. onFeatureUnselect is not triggered.
I suggest to select feature in showMyPopup() function. featureselected event will be fired and popup is created by onFeatureSelect(), and user can close popup both with popup's Close icon and unselecting feature on map.
But alas, there's a possible bug (or unexpected behaviour) in OL, when you select feature with code and try to unselect it with clickout. It's described here: http://lists.osgeo.org/pipermail/openlayers-users/2012-September/026349.html One possible fix is to set SelectControl.handlers.feature.lastFeature manually.
function showMyPopup(myID){
for(var a = 0; a < stations.features.length; a++){ //loop through all the features
var feature = stations.features[a];
if (feature.attributes.ID.value == myID) { //until it finds the one with the matching ID attribute
// select is your SelectFeature control
select.select(feature);
// Fix for unselect bug
select.handlers.feature.lastFeature = feature;
break;
}
}
}
I take a look in the OpenLayers sources and there is in Popup.js something like that ...
...
var closePopup = callback || function(e) {
this.hide();
OpenLayers.Event.stop(e);
};
OpenLayers.Event.observe(this.closeDiv, "touchend",
OpenLayers.Function.bindAsEventListener(closePopup, this));
OpenLayers.Event.observe(this.closeDiv, "click",
OpenLayers.Function.bindAsEventListener(closePopup, this));
...
It seems to me if you add your own closePopup function you need to call the hide function in your code.

How do I activate a feature + popup when clicking outside of a map in Openlayers?

I'm re-parsing the KML that's already been loaded onto the map similar to the example here:
http://openlayers.org/dev/examples/sundials.html and turning it into a clickable list that will center the map on the point clicked, and display the popup window for it.
This was really easy to do in Google Maps, but I can't find any similar Openlayers examples. Is there any easier way to do this? Something built-in that I'm missing?
HTML:
<ul id="locationTable">
</ul>
JS:
htmlRows = "";
for(var feat in features) {
// Build details table
featId = features[feat].id; // determine the feature ID
title = jQuery(f).filter('[name=TITLE]').text();
htmlRow = "<li>"+title+"</li>";
htmlRows = htmlRows + htmlRow;
}
jQuery('#locationTable').append(htmlRows);
And then for the selectFeature function:
function selectFeature(fid) {
for(var i = 0; i<kml.features.length;++i) {
if (kml.features[i].id == fid)
{
selected = new OpenLayers.Control.SelectFeature(kml.features[i]);
selected.clickFeature(); // make call to simulate Click event of feature
break;
}
}
}
I think you should remove the "selected.clickFeature" call, and instead create an event listener for the "featureselected" event in your feature layer:
OpenLayers.Layer.Vector
If you display the popup in that event, you will only have to find it and select it with your existing code, and remove the line
selected.clickFeature();
Sidenote: Can your feature server deliver data in other formats? WFS for instance? Parsing KML data shouldn't be needed.

Categories