Retrieve any "active" layers / overlays in a folium project - leaflet

Was wondering if there was any way of retrieving active layers/ overlays. I have found an existing solution in leaflet https://github.com/vogdb/Leaflet.ActiveLayers but can't find any implementation of this in folium.
Using folium's layer controller I can retrieve the names of the layers but cant see any boolean information such as if they're active or not.
layerController = folium.LayerControl().add_to(m)
print(layerController.overlays.items())
Current output:
list of names
The desired outcome would be that same list of names but with ', True' or ', False'

Related

ipywidgets, problem using a Text widget to filter options in a Select widget with automatic updates/ real-time interaction

I'm trying to use ipywidgets for the first time to make an interactive browser in Jupyter Notebooks of Census data using the census module. In general I'm having difficulty learning the widget library. My plan is to use the Text widget to enter a search term that will then filter the available census 'concepts' or table options (example: "Means of transportation to work") in a Selection or SelectMultiple widget. The selections made will then filter and show available data variables / columns for that census 'concept' or table (example: "car", "truck", "bus", "bike", "walk")
For reference, this first code block is what I got to work in a normal Jupyter Notebook with no widgets (at the bottom is the ipywidget attempt I'm having trouble with). I'm just starting to explore widgets and don't know anyone with experience with them. Any assistance is greatly appreciated!
*Note, running my code would require a census api key from census.gov/data/developers.html, the "Request a KEY" option on the left side menu, and change the ckey="" variable in the code below to your key. Keys are free, as it is publicly available government data.
#This section works for me as standalone code, not using ipywidgets
#Show all concepts to get an idea of search phrases to use
concepts_all = list([n for n in sorted(list([f"{t['description']}" for t in c.acs5.tables()]))]);
#This is a VERY long list, so I commented out printing it
#[print(c) for c in concepts_all]
#Apply a search phrase and limit options accordingly
concept_description_search_phrase = "Means of transportation to work by travel"
[print(f"{table['name']}: {table['description']}") for table in c.acs5.tables() if concept_description_search_phrase.lower() in table['description'].lower()]
#From the filtered options, chose one specific one to search, and display available data for it
concept_full_description = 'MEANS OF TRANSPORTATION TO WORK BY TRAVEL TIME TO WORK'
variable_filter_phrase = 'bicycle'
df = [pd.read_json(table['variables']) for table in c.acs5.tables() if table['description'] == concept_full_description][0]
acs_variable_names_list = [row.name for n,row in df.iterrows()
if variable_filter_phrase.lower() in row['variables']['label'].lower()
and 'Annotation' not in row['variables']['label']
and 'Margin of Error' not in row['variables']['label']]
acs_variable_names_list
# This is the final list I want out from a ipywidgets based workflow
# Which are variable columns, (ex: number of people in each census block group that
# have a 10, 15, 30, 45, 60+ minute commute by bicycle
########################################################
I'm having trouble getting the ipywidgets to work properly, where the Text box will interactively filter the available options in the Selection menu. I can only get it to work on a default value for the Text widget, but not off of the interactive menu. I'm basically going for the effect of the Combobox widget, but with the option to select more than one item if desired, and still see all the available options after a selection is made (so I'm thinking to use a Text and Select widget together instead of Combobox). This is my attempt so far:
out = widgets.Output()
display(out)
search_phrase = widgets.Text(
value = 'transportation',
layout = widgets.Layout(width = '900px')
)
select_multiple = widgets.SelectMultiple(
options = [n for n in concepts_all if search_phrase.value.lower() in n.lower()],
layout = widgets.Layout(width='900px',height='250px'),
ensure_option = True,
disabled=False
)
def search_change(change):
output.clear_output()
display(search_phrase)
with output:
if change.new == '':
print('new search')
return search_phrase.value
else:
print('same search')
return search_phrase.value
display(search_phrase)
display(select_multiple)
#Issue here with getting the search_phrase to properly filter options in select_multiple
#
concept_full_description = select_multiple.value[0] #Using Widgets
variable_filter_phrase = widgets.Text(value = 'bicy')
display(variable_filter_phrase) #Issue, this text box won't automatically filter the output
#it is only filtering by the default value
df = [pd.read_json(table['variables']) for table in c.acs5.tables() if table['description'] == concept_full_description][0]
acs_variable_names_list = [row.name for n,row in df.iterrows()
if variable_filter_phrase.value.lower() in row['variables']['label'].lower()
and 'Annotation' not in row['variables']['label']
and 'Margin of Error' not in row['variables']['label']] #print(row.name,": ",row['variables']['label'])
acs_variable_names_list
#This is the desired output list that will go into later workflows
I think I'm doing something wrong either in use of the .observe(), .interact(), or output /clear_output process. I'm just learning the ipywidgets and am even having trouble getting this to work in a basic example trying to follow the ipywidgets.readthedocs.io documentation.

How to query points with a polygon layer using Bootleaf / esri-leaflet?

I am using the Bootleaf IAG framework.
I can not figure out how to get the bounding coordinates of a filtered layer.
I am modifying the bootleaf code to query points with a polygon layer. The Query Widget already allows users to draw a polygon, but I want to select a polygon from a layer hosted on my arcgis server. I modified the filter widget by removing the text field and allowing my users to select polygon layers and values from a dropdown menu. This works fine.
Now I need to take the result of the layer.setWhere(where, handleError); code and merry it with the query below. I need selectedPolygon to equal the result of layer.setWhere(where, handleError); and use the bounding coordinates in the .within section of the query.
I have tried a number of things, L.latLngBounds, getBounds(), and toGeoJSON().features[0].geometry.coordinates to name a few, but but I can not figure out how to pull out the bounds. What is the correct code?
const query = L.esri.query({ url: pointInPolygonUrl })
.token(pointInPolygonData.token)
.within(selectedPolygon)
query.run(function (error, data, response) {
if (error) {
console.log(error);
return;
}
6/8/2021 Edit (based on Seth Lutske's comment:
I did not provide a code sandbox for two reasons: 1 - bootleaf has a lot of files, 2 - all of my layers require secure sign in to arcgis. Hopefully I can provide enough information to get assistance without it.
Is selectedPolygon changing the way I am expecting? Currently there
is no variable called selectedPolygon because I can not figure out
the correct way to format it. selectedPolygon is what I want to
call the filter result layer.setWhere(where, handleError);. I set
the polygon layer up to filter on the map as the value changes. I
can verify it is filtering as expected.
selectedPolygon format - This is where my problem lies. I can not
seem to find the correct format based on how bootleaf layers are
configured. I started with var selectedPolygon =
layer.features.geometry.coordinates; and got a geometry undefined
error. I proceeded to try every other code I could think of to get
the bounds.
Bounding coordinates may not be the proper terminology. I want to
run a query to find all of the points within the filtered polygon.
To achieve this, it is my understanding that I need to use the
bounds of the filtered polygon in the within section of the query.
6/8/2021 Edit #2
This link may be most beneficial to show how the layer is constructed. I modified this code to remove the text input and add a dropdown, but the basic definition should be the same.
Line 1605 is function addFilter()
Line 1804 is function applyFilter()
Line 1927 is layer.setWhere(where, handleFilterError);
Photo 1: console.log("polygon layer", layer)
Photo 1
Photo 2: Expanded _layers
Photo 2
Photo 3: Expanded _rings (I did not find ToGetJSON, but I found ToGeoJSON in this section.
Photo 3
It looks like if I can get to _rings then I should be fine, but that is where my knowledge is lacking.
I don't know much about bootleaf, but here are some tips to get you started. Based on your question and comments, this will hopefully clear things up and instruct you on how to apply what you need in your scenario.
Hook UI to setWhere
When the user selects an option from the UI, you can call setWhere on the layer you're providing from the arcgis server. Let's say there's a polygon layer, in my example, called statesFeatureLayer, which is an L.esri.featureLayer
// Create polygon layer of states
const statesFeatureLayer = EL.featureLayer({
url: "polygon_featurelayer_url_from_arcgis_server"
}).addTo(map);
And there's a point layer:
// Create points layer
const pointsFeatureLayer = EL.featureLayer({
url: "points_featurelayer_url"
}).addTo(map);
Now there is some UI, which has to trigger setWhere to be called on this layer. So anywhere in the UI where you want to run this functionality of setting the filter on the layer, and then querying the other layer based on the results, we'll run a function call runQuery:
function runQuery(){
statesFeatureLayer.setWhere(querystring, callback)
}
Run callback after setWhere fires
It sounds like you've already got this part figured out, and that your setWhere function is running properly. However, setWhere also takes an optional callback function as its second argument, which runs after the where has been set and the layer refreshed. Let's dig into that. In the callback, we're going to want to get all the features that are currently active on the map:
function runQuery(){
statesFeatureLayer.setWhere(querystring, () => {
statesFeatureLayer.eachActiveFeature(feature => {
// do something with features
})
})
}
Run query to test points layer against active features of polygon layer
Within eachActiveFeature, we can run a query on the pointsFeatureLayer:
function runQuery(){
statesFeatureLayer.setWhere(querystring, () => {
statesFeatureLayer.eachActiveFeature(feature => {
pointsFeatureLayer
.query()
.within(feature.toGeoJSON())
.run((error, data) => {
console.log(data);
});
})
})
}
So now were are running a query which asks for any points in the pointsFeatureLayer that are in the geometry of each active feature of the statesFeatureLayer.
The downside of this is that we can't run a query against all the active features as a group. The within query method (along with most of the other query methods) can accept singular features, whether in the form of an L.Polygon, L.Polyline, or an L.GeoJSON. While I had tried creating an L.featureGroup and calling .toGeoJSON on that, within seems to require a GeoJSON that describes only a single shape. So if you have multiple features, you'll have to conglomerate them. For example, you may have some variable results = [] at the global scope level, then within the callback of run, you can push the results to results, which will give you all results in one variable. This may take some massaging in js to get it right.
Working Codesandbox
Here you have 2 UI elements which cause runQuery to run. Either the dropdown, or the checkbox. You'll see that on every UI change, setWhere is called with a querystring constructed from the UI (setWhere for a state, and setwhere for that state and california if the checkbox is checked). When setWhere is called, its callback then runs a query against the point layer just for the currently active features, and then returns whatever points from the pointlayer are within each of the active features.

Gremlin: Generate a list by location of counts for active versus inactive users

I have vertices people, usertype, and location. People has outgoing edges people_location and people_usertype. People has property 'name', usertype has property 'activationStatus', and location has property 'name'.
I want to create a list that looks like this:
[[1]: https://i.stack.imgur.com/lKzZL.png]
I want the count of people, by location, for activationStatus "active" and "inactive" where the location has "US" in it.
This is all I have only for count of people by location where the location 'name' begins with US:
g.V()hasLabel('people').out('people_publicisofficelocation')
.filter(has('name',between('US','UT')))
.groupCount().by('name')
It is running but not yielding results.
You can simulate 'starts with' behavior in versions of TinkerPop prior to 3.4 using something like has('name',between('US','UT')) so you could replace the filter line above with that. If the graph implementation you are using supports TinkerPop 3.4 there are additional text predicates you can use for begins with, ends with and contains.
As others have said if you can post some sample addV() and addE() steps that build part of your graph it will be easier to give a more precise answer.
This worked for me!
g.V().hasLabel('Location').filter(has('name',between('US','UT')))
.project('Name','Active', 'Inactive', 'Total')  .by('name')  .by(__.both('people_location').out('people_usertype')
.where(values('activationStatus').is(eq('Active'))).count())  .by(__.both('people_location').out('people_usertype')
.where(values('activationStatus').is(eq('Inactive'))).count()) 
.by(__.both('people_location').out('people_usertype').count())

Get the number of features displayed on a map after a filter (with featuresIn)

First of all, I'm aware that querySourceFeatures could fix that. But unfortunately the new version is effective with all types but not with symbols, which I'm using. So I'm still coding under version 0.14.x.
That said, I filter my map with the setFilter function and I need to catch back the number of features displayed once the filter is done.
I thought about transform the whole world (-90,-180,90,180) map coordinates into pixels and then pass it into a featuresIn function.
With fiddle below, featuresIn returns nothing [EDIT : that was due to not setting interacive : true, now it's done but issue is still here]. Do you have any idea how to get the number of features displayed on my map?
EDIT : Please find my jsFiddle : https://jsfiddle.net/y7hoa0gy/7/
No features are being returned from featuresIn because you did not set "interactive": true on the "route" layer, as specified in the documentation (but no longer on our official docs page because we have changed this API).
/*Now I want to know how many features are still displayed after that filter
My thought was to get the bbox of the whole map (-180,-90,180,90) and make a featuresIn of that bbox.*/
More fundamentally, this approach will not work. featuresIn only returns features in the current viewport. It does not return all features.

Mapbox-gl-js filtering marker clusters

We can now cluster markers in gl-js:
https://www.mapbox.com/mapbox-gl-js/example/cluster/
Is there an equivalent way of marker filters in clustering in mapbox-gl-js as in leaflet like this example:
https://www.mapbox.com/mapbox.js/example/v1.0.0/filtering-marker-clusters/
I have also run into this issue and believe there should be a way of further filtering cluster layers based on a property of the features in the collection. But from my understanding (and I sincerely hope there is a better way that I just haven't figured out yet), there isn't a way to distinguish the clustered features since you declare on the source that it is to be clustered. A workaround that I have come up with is to dynamically add a source and layer as your filter changes.
For example: if you're filtering by subcategory ID's, you can pare down your original FeatureCollection that match that subcategory ID and create a new source with that FeatureCollection with cluster set to true, add your layer for the markers, and then add your cluster layers like they have laid out in their example. And anytime that filter changes, you can either toggle the visibility of that marker layer (haven't tested that part) or just remove it and add the new one repeating those previous steps. Some of the many downsides to this approach is the inability to use your regular filters on the whole dataset (if you had any) and it isn't as performant as if you were using filters.
If you use the earthquakes data from mapbox, the code might look like:
var data = "https://www.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson";
var filterID = 1.87; // Arbitrary number
var paredData = _.filter(data.features, function(feature){
if (feature["Primary ID"] === filterID) {
return feature;
}
})
// Remove all of the layers and source associated
// with a previous filter if needed
if (map.getSource("earthquake-filter") {
map.removeLayer("earthquake-filter");
map.removeLayer("earthquake-filter-cluster");
map.removeLayer("earthquake-filter-cluster-count");
map.removeSource("earthquake-filter");
}
map.addSource("earthquake-filter", {
type: "geojson",
data: {
type: "FeatureCollection",
features: paredData
},
cluster: true,
clusterMaxZoom: 14,
clusterRadius: 50
});
And continue as they do in the example.
Not my favorite solution but it's the only one that works so far that I've found. Hope this helps.