Python folium synchronizing 2 markerclusters - python

I have a situation, where I decided to distinguish my markers slightly by adding the MarkerCluster to the existing Marker.
The clustering works, although isn't synchronized. It means, that if I switch one layer off, just the first markercluster disappears, whereas the second criterion defined as CircleMarker still appears like shown below.
My code is:
df = pd.read_csv("or_geo.csv")
fo=FeatureGroup(name="OR",overlay = True)
openreach_cluster = MarkerCluster(name="OR").add_to(map)
openreach_status = MarkerCluster(control=False,
visible=True
).add_to(map)
for i,row in df.iterrows():
lat =df.at[i, 'lat']
lng = df.at[i, 'lng']
sp = df.at[i, 'sp']
stat = df.at[i,'status']
popup = df.at[i,'sp'] +'<br>' + str(df.at[i, 'street']) + '<br>' + str(df.at[i, 'post code']) + '<br>{}'.format(style)
or_marker = folium.Marker(location=[lat,lng], tooltip='<strong>Job details</strong>', popup=popup, icon = folium.Icon(
color='blue', icon='glyphicon-calendar'))
or_stat_marker = folium.CircleMarker(
location=[lat,lng],
radius=10,
color=or_color(stat),
fill_color=or_color(stat),
fill_opacity=0.5)
openreach_cluster.add_child(or_marker)
openreach_status.add_child(or_stat_marker)
Is there any way to combine these markerClusters together or sync them?
UPDATE:
The first approach from the answer below, unfortunately, doubles the jobs up and the user can't see them until clicks on any of them shown below:
This option would be fantastic if the behavior could be the same as in the image above.
UPDATE II:
The second approach is still not what I am looking for, because the clusters are doubled again and after clicking the circlemarker falls almost in the opposite direction as presented in the image above.
I need to have the behaviors exactly like those displayed on the top-left image. The circlemarker should be integrated with the point marker.

If I got you right, I think there are two possibilities:
Use the same cluster
Add your markers from or_stat_marker to the openreach_cluster and not to another cluster to de-/activate them at the same time with the same button
# was
openreach_cluster.add_child(or_marker)
openreach_status.add_child(or_stat_marker)
# try this
openreach_cluster.add_child(or_marker)
openreach_cluster.add_child(or_stat_marker)
Use marker subgroups
In this case you will have three checkmarks, one parent and two childs for each markercluster which gives full control to visibility
UPDATE: If you add the control=False option to the subgroup you will only see the parent group in LayerControl which then show/hide both groups. But the matter of markers "spreading for visibility" still is a problem I think
Another UPDATE: You are able to deactivate the clustering according to the map zoom level or even at all by using disableClusteringAtZoom option (use True or an int number for zoom level). See here for reference
# markergroups in layercontrol
mc = folium.plugins.MarkerCluster(name='OR',
overlay=True,
control=True,
show=True,
disableClusteringAtZoom=15) # choose zoom lvl to your needs
mc.add_to(map)
sub1 = folium.plugins.FeatureGroupSubGroup(mc, name='openreach_cluster', control=False, show=True) # False --> deactivated at start
sub1.add_to(map)
sub2 = folium.plugins.FeatureGroupSubGroup(mc, name='openreach_status', control=False)
sub2.add_to(map)
# the layercontrol itself
lc = folium.map.LayerControl(collapsed=False)
lc.add_to(map)
# ...
for i, row in df.iterrows():
# ...
or_marker = folium.Marker(...)
or_marker.add_to(sub1)
or_stat_marker = folium.CircleMarker(...)
or_stat_marker.add_to(sub2)
My Result:
By following the second approach described I get a map shown here. There is one checkmark "Segment Markers" which shows/hides the markers including the circles. They don't move around when clicked and are fully shown when zoomed in to a specific lvl by using disableClusteringAtZoom.
Sorry this didn't work for your problem, I really don't know why ..

Related

How to get rid of this popup when hovering over a label in the Chord Diagram (Holoviews - Python)

Exactly like the title says, I've used some code that I collected from multiple places to build up this Chord diagram, but unfortunately one last thing that's kind of going to hinder that 100% perfection is this popup that shows up whenever I hover over the label. The pop up remains there even when the mouse is moving elsewhere...
By the way, I'm using Python and Holoviews for plotting the chord.
I'm pretty sure this is due to a bug or something... however, I'd love to find a way to bypass it...
Example here : bug example
Code :
%%opts Chord [height=600 width=600 title="Transactions from 2000 to 2021" ]
#plot
#edit links = moves[moves['Transactions'] > x] // x refers to the number of transactions minimum for the diagram display
moves = moves[moves['Transactions']>5]
links = moves
chord = hv.Chord(links)
chord
nodesl = []
c = []
for i, row in moves.iterrows():
c.append(row[0])
c.append(row[1])
c = pd.DataFrame(c)
c.drop_duplicates(inplace=True)
c.reset_index(inplace=True)
c.drop(columns='index', inplace=True)
c
nodes = []
for i, row in c.iterrows():
nodes.append({'name':row[0]})
nodes = pd.DataFrame(nodes)
# nodes
nodes = hv.Dataset(nodes, 'name')
nodes.data.head()
%%opts Chord [height=800 width=800 bgcolor="black"]
%%opts Chord [title="Transactions from 2000 to 2021 (Countries with over 5 moves)\nTip: Please do not hover over the label as it might produce a bug, else refresh the page" ]
chord = hv.Chord((links, nodes)).select(value=(5, None))
#this function allows text to fit perfectly on the screen
def rotate_label(plot, element):
text_cds = plot.handles['text_1_source']
length = len(text_cds.data['angle'])
text_cds.data['angle'] = [0]*length
xs = text_cds.data['x']
text = np.array(text_cds.data['text'])
xs[xs<0] -= np.array([len(t)*0.019 for t in text[xs<0]])
chord.opts(
opts.Chord(cmap='Category10',
edge_color=dim('Target').str(),
node_color=dim('name').str(),
labels='name',
label_text_color="white",
hooks=[rotate_label]
))
chord
The ??? usually indicates that some field it's trying to display isn't available; not sure why that would be in this case. You can always override the default tools HoloViews uses for Bokeh by setting default_tools=[] and then specify whatever tools you do want, without 'hover', e.g. tools=['save', 'pan', 'wheel_zoom', 'box_zoom', 'reset'].

Strange behaviour when function returns NdOverlay to DynamicMap

I've encountered something very strange when having a function which generates an NdOverlay of Points to a DynamicMap, where the function is tied to panel widgets (I don't think the panel widgets are important).
The below code is a working example which produces the expected behavior. Whenever you change the widget values a new plot is generated with two sets of Points overlaid, with different colors and respective legend entries. Image shown below code.
a_widget = pn.widgets.Select(name='A', options=[1,2,3,4])
b_widget = pn.widgets.IntSlider(name='B', start=10, end=20, value=10)
widget_box = pn.WidgetBox(a_widget, b_widget, align='center')
#pn.depends(a=a_widget.param.value, b=b_widget.param.value)
def get_points(a, b):
return hv.NdOverlay({x: hv.Points(np.random.rand(10,10)) for x in range(1,3)})
points = hv.DynamicMap(get_points)
pn.Row(widget_box, points)
The second example shown below, is meant to demonstrate that in certain situations you might want to just simply return an empty plot and the way that I've done it in this example is done in the same way as in this example: http://holoviews.org/gallery/demos/bokeh/box_draw_roi_editor.html#bokeh-gallery-box-draw-roi-editor
The result of this code is an empty plot as expected when a == 1, but when a has values other than 1, the result is quite strange as illustrated in the image below the code.
The points all have the same color
When changing the slider for instance, some points are frozen and never changes, which is not the case in the above working example.
a_widget = pn.widgets.Select(name='A', options=[1,2,3,4])
b_widget = pn.widgets.IntSlider(name='B', start=10, end=20, value=10)
widget_box = pn.WidgetBox(a_widget, b_widget, align='center')
#pn.depends(a=a_widget.param.value, b=b_widget.param.value)
def get_points(a, b):
if a == 1:
return hv.NdOverlay({None: hv.Points([])})
else:
return hv.NdOverlay({x: hv.Points(np.random.rand(10,10)) for x in range(1,3)})
points = hv.DynamicMap(get_points)
pn.Row(widget_box, points)
While I can not help the observed issue with NdOverlay, creating plots with or without content can be done with the help of Overlay.
As b_widget is never used in your code, I removed it for simplicity.
a_widget = pn.widgets.Select(name='A', options=[1,2,3,4])
widget_box = pn.WidgetBox(a_widget, align='center')
#pn.depends(a=a_widget.param.value)
def get_points(a):
images = []
if a == 3:
images.append(hv.Points(np.random.rand(10,10), label='None'))
else:
for x in range(1,3):
images.append(hv.Points(np.random.rand(10,10), label=str(x)))
return hv.Overlay(images)
points = hv.DynamicMap(get_points)
pn.Row(widget_box, points)
The way how to use NdOverlay that is described in the documentation for NdOverlay is different to your approach, this might be a reason for the observed problems.
Anyway, to narrow down which part of the code is responsible for the observed issue, I removed all code that is not necessary to reproduce it.
For clarity, I renamed the values of a, and I also made sure, that a start value for a is provided.
It turned out while testing the code, that the if-else-statement is neither important, so I removed that too.
And just to make sure, that variables behave like expected, I added some print-statements.
This gives the following minimal reproducable example:
a_widget = pn.widgets.Select(name='A', value='Test', options=['Test','Test1', 'Test2'])
#pn.depends(a=a_widget.param.value)
def get_points(a):
dict_ = {}
dict_[str(a)] = hv.Points(np.random.rand(10,10))
print(dict_)
overlay = hv.NdOverlay(dict_)
print(overlay)
return overlay
points = hv.DynamicMap(get_points)
# using the server approach here to see the outpout of the
# print-statements
app = pn.Row(a_widget, points)
app.app()
When running this code, and choosing the different options in the select widget, it turns out that option Test is not updated, once one of the options Test1 and Test3 have been choosen.
When we change the default value in the first line like this
a_widget = pn.widgets.Select(name='A', value='Test2', options=['Test','Test1', 'Test2'])
now Test2 is not updated correctly.
So it looks like this is an issue of DynamicMap using NdOverlay.
So I suggest you report this issue to the developers (if not already done), either wait for new release or use a different approach (e.g. as shown above).

how to draw several paths on different files in gmplot?

I have several vehicle paths and I would like to automatically draw all of them on separate files. I am trying to do it with a for loop, but the points end up overlapping on each following file. So, basically, on the last file, I have all paths.
This is my function. Can someone help me with this?
def drawUnique(uniqueVeh):
for i in uniqueVeh:
latitudes = list(map(float,list(gps_data[gps_data["id"] == i]["lat"])))
longitudes = list(map(float,list(gps_data[gps_data["id"] == i]["long"])))
gmap.scatter(latitudes, longitudes, size=10, marker=False)
gmap.draw("map" + i + ".html")
The issue is related to the declaration of the gmap object, that is made obviously before the loop, so the same object is used and saves all the marks.
You just need to define a new gmap object at the beginning of each iteration to create a fresh new map:
def drawUnique(uniqueVeh):
for i in uniqueVeh:
gmap = gmplot.GoogleMapPlotter(center_lat, center_lng, zoom) # replace the values !!
latitudes = list(map(float,list(gps_data[gps_data["id"] == i]["lat"])))
longitudes = list(map(float,list(gps_data[gps_data["id"] == i]["long"])))
gmap.scatter(latitudes, longitudes, size=10, marker=False)
gmap.draw("map" + i + ".html")
Basically the code in the question can be converted like below:
import gmplot
gmap = gmplot.GoogleMapPlotter(40.640, -73.926, 16)
# turn 1
gmap.scatter([40.642810, 40.638240],
[-73.915, -73.922901],
'cornflowerblue', edge_width=8)
gmap.draw("map1.html")
# turn 2
# same gmap : all marks are added and overlap the first
gmap.scatter([40.644494, 40.637083],
[-73.925044, -73.926464],
'red', edge_width=8)
gmap.draw("map2.html")
You need to insert this line between each drawing to avoid the overlapping issue in map2.html :
gmap = gmplot.GoogleMapPlotter(40.640, -73.926, 16)

Count number of points in multipolygon shapefile using Python

I have a polygon shapefile of the U.S. made up of individual states as their attribute values. In addition, I have arrays storing latitude and longitude values of point events that I am also interested in. Essentially, I would like to 'spatial join' the points and polygons (or perform a check to see which polygon [i.e., state] each point is in), then sum the number of points in each state to find out which state has the most number of 'events'.
I believe the pseudocode would be something like:
Read in US.shp
Read in lat/lon points of events
Loop through each state in the shapefile and find number of points in each state
print 'Here is a list of the number of points in each state: '
Any libraries or syntax would be greatly appreciated.
Based on what I can tell, the OGR library is what I need, but I am having trouble with the syntax:
dsPolygons = ogr.Open('US.shp')
polygonsLayer = dsPolygons.GetLayer()
#Iterating all the polygons
polygonFeature = polygonsLayer.GetNextFeature()
k=0
while polygonFeature:
k = k + 1
print "processing " + polygonFeature.GetField("STATE") + "-" + str(k) + " of " + str(polygonsLayer.GetFeatureCount())
geometry = polygonFeature.GetGeometryRef()
#Read in some points?
geomcol = ogr.Geometry(ogr.wkbGeometryCollection)
point = ogr.Geometry(ogr.wkbPoint)
point.AddPoint(-122.33,47.09)
point.AddPoint(-110.11,33.33)
#geomcol.AddGeometry(point)
print point.ExportToWkt()
print point
numCounts=0.0
while pointFeature:
if pointFeature.GetGeometryRef().Within(geometry):
numCounts = numCounts + 1
pointFeature = pointsLayer.GetNextFeature()
polygonFeature = polygonsLayer.GetNextFeature()
#Loop through to see how many events in each state
I like the question. I doubt I can give you the best answer, and definitely can't help with OGR, but FWIW I'll tell you what I'm doing right now.
I use GeoPandas, a geospatial extension of pandas. I recommend it — it's high-level and does a lot, giving you everything in Shapely and fiona for free. It is in active development by twitter/#kajord and others.
Here's a version of my working code. It assumes you have everything in shapefiles, but it's easy to generate a geopandas.GeoDataFrame from a list.
import geopandas as gpd
# Read the data.
polygons = gpd.GeoDataFrame.from_file('polygons.shp')
points = gpd.GeoDataFrame.from_file('points.shp')
# Make a copy because I'm going to drop points as I
# assign them to polys, to speed up subsequent search.
pts = points.copy()
# We're going to keep a list of how many points we find.
pts_in_polys = []
# Loop over polygons with index i.
for i, poly in polygons.iterrows():
# Keep a list of points in this poly
pts_in_this_poly = []
# Now loop over all points with index j.
for j, pt in pts.iterrows():
if poly.geometry.contains(pt.geometry):
# Then it's a hit! Add it to the list,
# and drop it so we have less hunting.
pts_in_this_poly.append(pt.geometry)
pts = pts.drop([j])
# We could do all sorts, like grab a property of the
# points, but let's just append the number of them.
pts_in_polys.append(len(pts_in_this_poly))
# Add the number of points for each poly to the dataframe.
polygons['number of points'] = gpd.GeoSeries(pts_in_polys)
The developer tells me that spatial joins are 'new in the dev version', so if you feel like poking around in there, I'd love to hear how that goes! The main problem with my code is that it's slow.
import geopandas as gpd
# Read the data.
polygons = gpd.GeoDataFrame.from_file('polygons.shp')
points = gpd.GeoDataFrame.from_file('points.shp')
# Spatial Joins
pointsInPolygon = gpd.sjoin(points, polygons, how="inner", op='intersects')
# Add a field with 1 as a constant value
pointsInPolygon['const']=1
# Group according to the column by which you want to aggregate data
pointsInPolygon.groupby(['statename']).sum()
**The column ['const'] will give you the count number of points in your multipolygons.**
#If you want to see others columns as well, just type something like this :
pointsInPolygon = pointsInPolygon.groupby('statename').agg({'columnA':'first', 'columnB':'first', 'const':'sum'}).reset_index()
[1]: https://geopandas.org/docs/user_guide/mergingdata.html#spatial-joins
[2]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.groupby.html

dynamic plotting in wxpython

I have been developing a GUI for reading continuous data from a serial port. After reading the data, some calculations are made and the results will be plotted and refreshed (aka dynamic plotting). I use the wx backend provided in the matplotlib for this purposes. To do this, I basically use an array to store my results, in which I keep appending it to, after each calculation, and replot the whole graph. To make it "dynamic", I just set the x-axis lower and upper limits for each iteration. Something like found in:
http://eli.thegreenplace.net/2008/08/01/matplotlib-with-wxpython-guis/
The problem, however, is that since the data is continuous, and if I keep plotting it, eventually the system memory will run out and system will crash. Is there any other way I can plot my result continuously?
To do this, I basically use an array
to store my results, in which I keep
appending it to
Try limiting the size of this array, either by deleting old data or by deleting every n-th entry (the screen resolution will prevent all entries to be displayed anyway). I assume you write all the data to disk so you won't lose anything.
Also, analise your code for memory leaks. Stuff you use and don't need anymore but that doesn't get garbage-collected because you still have a reference to it.
I have created such a component with pythons Tkinter. The source is here.
Basically, you have to keep the plotted data somewhere. You cannot keep an infinite amount of data points in memory, so you either have to save it to disk or you have to overwrite old data points.
Data and representation of data are two different things. You might want to store your data to disk if it's important data to be analyzed later, but only keep a fixed period of time or the last N points for display purposes. You could even let the user pick the time frame to be displayed.
I actually ran into this problem (more of a mental block, actually...).
First of all I copy-pasted some wx Plot code from wx Demo Code.
What I do is keep a live log of a value, and compare it to two markers (min and max, shown as red and green dotted lines) (but I will make these 2 markers optional - hence the optional parameters).
In order to implement the live log, I first wanted to use the deque class, but since the data is in tuple mode (x,y coordinates) I gave up and just tried to rewrite the entire parameter list of tuples: see _update_coordinates.
It works just fine for keeping track of the last 100-10,000 plots. Would have also included a printscreen, but I'm too much of a noob at stackoverflow to be allowed :))
My live parameter is updated every 0.25 seconds over a 115kbps UART.
The trick is at the end, in the custom refresh method!
Here is most of the code:
class DefaultPlotFrame(wx.Frame):
def __init__(self, ymin=0, ymax=MAXIMUM_PLOTS, minThreshold=None,
maxThreshold=None, plotColour='blue',
title="Default Plot Frame",
position=(10,10),
backgroundColour="yellow", frameSize=(400,300)):
self.minThreshold = minThreshold
self.maxThreshold = maxThreshold
self.frame1 = wx.Frame(None, title="wx.lib.plot", id=-1, size=(410, 340), pos=position)
self.panel1 = wx.Panel(self.frame1)
self.panel1.SetBackgroundColour(backgroundColour)
self.ymin = ymin
self.ymax = ymax
self.title = title
self.plotColour = plotColour
self.lines = [None, None, None]
# mild difference between wxPython26 and wxPython28
if wx.VERSION[1] < 7:
self.plotter = plot.PlotCanvas(self.panel1, size=frameSize)
else:
self.plotter = plot.PlotCanvas(self.panel1)
self.plotter.SetInitialSize(size=frameSize)
# enable the zoom feature (drag a box around area of interest)
self.plotter.SetEnableZoom(False)
# list of (x,y) data point tuples
self.coordinates = []
for x_item in range(MAXIMUM_PLOTS):
self.coordinates.append((x_item, (ymin+ymax)/2))
self.queue = deque(self.coordinates)
if self.maxThreshold!=None:
self._update_max_threshold()
#endif
if self.lockThreshold!=None:
self._update_min_threshold()
#endif
self.line = plot.PolyLine(self.coordinates, colour=plotColour, width=1)
self.lines[0] = (self.line)
self.gc = plot.PlotGraphics(self.lines, title, 'Time', 'Value')
self.plotter.Draw(self.gc, xAxis=(0, MAXIMUM_PLOTS), yAxis=(ymin, ymax))
self.frame1.Show(True)
def _update_max_threshold(self):
if self.maxThreshold!=None:
self.maxCoordinates = []
for x_item in range(MAXIMUM_PLOTS):
self.maxCoordinates.append((x_item, self.maxThreshold))
#endfor
self.maxLine = plot.PolyLine(self.maxCoordinates, colour="green", width=1)
self.maxMarker = plot.PolyMarker(self.maxCoordinates, colour="green", marker='dot')
self.lines[1] = self.maxMarker
#endif
def _update_live_param(self, liveParam, minParam, maxParam):
if minParam!=None:
self.minThreshold = int(minParam)
self._update_min_threshold()
#endif
if maxParam!=None:
self.maxThreshold = int(maxParam)
self._update_max_threshold()
#endif
if liveParam!=None:
self._update_coordinates(int(liveParam))
#endif
def _update_coordinates(self, newValue):
newList = []
for x,y in self.coordinates[1:]:
newList.append((x-1, y))
#endfor
newList.append((x, newValue))
print "New list", newList
self.line = (plot.PolyLine(newList, colour=self.plotColour, width=1))
self.lines[0] = self.line
self.coordinates = newList
def _MyLIVE_MAGIC_refresh__(self, liveParam=None, minParam=None, maxParam=None):
self._update_live_param(liveParam, minParam, maxParam)
self.gc = plot.PlotGraphics(self.lines, self.title, 'Time', 'Value')
self.plotter.Draw(self.gc, xAxis=(0, MAXIMUM_PLOTS), yAxis=(self.ymin, self.ymax))
self.plotter.Refresh()
self.frame1.Refresh()

Categories

Resources