How can I highlight multiple search results in Python folium/leaflet? - python

I have plotted graph in Python using folium/Leaflet with search. Problem I am facing is that it highlights only one result on map even if there are multiple results.
For example if I search by name then it works fine as mostly there is only 1 person with that full name.
But if I search on a city for example 'Delhi' then it results in highlighting only 1 marker instead of 7 or 10.
In the image below: There is only 1 marker circled in red which it is pointing as result but there others from same city which are not highlighted.
And how can I change highlighting properties to something more noticeable?
Image:
Here is the code:
######################################################################
# Part 1 - Creating map
m_search = folium.Map(location=[28.6003435, 77.21952300000001],zoom_start=11
)
#######################################################################
# Part 2 - Creating folium markers on map with html text, images
# etc dynamically for multiple points using for-loop
for plot_numb in range(gdf.shape[0]):
icon = folium.Icon(color="blue", icon="cloud", icon_color='white')
tooltip = 'Click to view more about: '+gdf.iloc[plot_numb,0]
var_name = gdf.iloc[plot_numb,0]
var_loc = gdf.iloc[plot_numb,2]
pic = base64.b64encode(open('Images/'+gdf.iloc[plot_numb,5],'rb').read()).decode()
html = f'''<img ALIGN="Right" src="data:image/png;base64,{pic}">\
<strong>Name: </strong>{var_name}<br /><br />\
<strong>Location: </strong>{var_loc}<br /><br />\
'''
html
iframe = IFrame(html, width=300+180, height=300)
popup = folium.Popup(iframe, max_width=650)
marker = folium.Marker(location=gdf.iloc[plot_numb,1], popup=popup, tooltip=tooltip, icon=icon).add_to(m_search)
########################################################################
# Part 3 - Creating Markers using GeoJson, creating search
# creating folium GeoJson objects from out GeoDataFrames
pointgeo = folium.GeoJson(gdf,name='group on map', show=False,
tooltip=folium.GeoJsonTooltip(fields=['Name', 'Relation', 'City'], aliases=['Name','Relation', 'City'],
localize=True)).add_to(m_search)
# Add some Search boxes to the map that reference the GeoDataFrames with some different parameters passed to the
# arguments.
pointsearch = Search(layer=pointgeo, geom_type='Point',
placeholder='Search for contacts', collapsed=False,
search_label='Name').add_to(m_search)
# To Add a LayerControl add below line
folium.LayerControl().add_to(m_search)
m_search
Below is the example of the gdf Data Frame:
Name Location City Relation Relation Detail Image Lat Lon geometry
0 abc [28.562193, 77.387073] Noida Cousin Cousin 1.png 28.562193 77.387073 POINT (77.387073 28.562193)
1 def [28.565282027743955, 77.44913935661314] Noida Cousin Cousin Brother 2.png 28.565282 77.449139 POINT (77.44913935661314 28.56528202774395)
3 ghi [28.6206996683473, 77.42576122283936] Noida Cousin Cousin Brother 4.png 28.620700 77.425761 POINT (77.42576122283936 28.6206996683473)
Need some help here as I am new to coding and unable to figure out.

Related

Python folium synchronizing 2 markerclusters

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 ..

extract .dxf data using ezdxf to add to a pandas dataframe

The end goal is to extract the text contained on a specific layer, inside of a named view, from the model space. I have the layer restriction (the text in yellow for visual), but I can't seem to figure out the syntax (if possible) to limit the query to either items inside one of the named views, or within a bounding box that I define to match the named view (orange box). The text being queried is single text. It is (and will always be) an exploded table. Each text item has a unique .insert value.
Ultimately this above mentioned query loop would be put inside a loop to iterate over all named views (bounding box) inside of the model space. Each of the iterations creating a unique list containing the query results. The list would then be input into a pandas dataframe for further manipulations.
import ezdxf
filepath = "C:/MBS_JOBS-16/8741-22/8741-22_(DTL).dxf"
doc = ezdxf.readfile(filepath)
msp = doc.modelspace()
ls = []
for e in msp.query('TEXT MTEXT[layer=="text"]'):
ls.append(e.dxf.text)
print(ls)
The ezdxf package does not have a feature for selecting entities based on location and size, but a bounding box based implementation is relatively easy.
It is important to know that bounding boxes of text based entities (TEXT, MTEXT) are inaccurate because matplotlib (used by ezdxf to render text) renders TTF fonts differently than AutoCAD.
I created an example DXF file with 6 views, called "v1", "v2", ...:
The following code prints the the content of view "v1": ["Text1a", "Text1b"]
import ezdxf
from ezdxf.math import BoundingBox, Vec2
from ezdxf import bbox
def get_view_box(view):
center = Vec2(view.dxf.center)
size = Vec2(view.dxf.width, view.dxf.height)
bottom_left = center - (size / 2)
top_right = center + (size / 2)
return BoundingBox([bottom_left, top_right])
doc = ezdxf.readfile("views.dxf")
msp = doc.modelspace()
view = doc.views.get("v1")
view_box = get_view_box(view)
for e in msp.query("TEXT MTEXT"):
text_box = bbox.extents([e]) # expects a list of entities!
if view_box.contains(text_box):
print(e.dxf.text)

Customize PyGal WorldMap chart's tooltip

I'm using pygal for generate a world map chart in Google Colab.
The problem I'm facing is with the tooltip. I want to show only in the tooltip the name of the country and the number of videos found.
The result so far I've managed to get is as follows:
Worldmap chart current output:
You can see there that the tooltip is composed as follows:
Country name
Number of videos
Country name: 1
Example:
United States
73 videos
Unites States: 1
Using the sample shown in the screenshot and in the sample above, what I want to get is
Example:
United States
73 videos
OR:
Example:
Unites States: 73 videos
Here is the Google Colab notebook sample I've created, so anyone can reproduce this issue.
This is the Worldmap chart settings:
# Save chart as svg:
worldmap_chart.render_to_file('worldmap_result.svg',
show_legend=True,
human_readable=True,
fill=True,
print_labels=True,
no_data_text="No data available",
legend_at_bottom=True,
pretty_print=True,
x_title="Hover the cursor on the country's name bellow or the map above:",
print_values=True,
force_uri_protocol='https')
I've readed the documentation and searched, but, I'm not been able to find the correct configuration (if possible) for generate the expected tooltip.
Does anyone knows what I'm missing?
After testing for a few more hours, I finally decided to follow these steps:
Generate/render the world chart in a file - in this case, a .svg file.
Read the generated file in the previous step, find and replace certain svg tags - those that contains the data I don't want in the final chart.
Download the modified file.
The said svg tags are as follows:
Example:
<desc class="value">Russian Federation: 1</desc>
Thanks to this answer, I was able to replace the svg tags and leave them empty. With that change, I was able to generate the world map chart as I required.
This is the full, modified and working code:
import types
# Library import:
from pygal_maps_world.maps import World
from pygal.style import DefaultStyle
worldmap_chart = World()
worldmap_chart.title = 'Channels distribution per country'
#list - source: https://stackoverflow.com/a/26716774/12511801
# Excludes "N/A" values:
countriesData = df_countries.loc[df_countries['Country (channel)'] != "N/A"].to_dict('split')["data"]
# Loop the "countriesData" and replace the name of the country
# with is ISO code - lowercase - in order to render the world map:
for x in countriesData:
countryName = x[0]
countryName = [x["id"].lower() for x in all_countries if x["name"] == countryName][0]
x[0] = countryName
# Strings to replace:
js_strings = []
# Generate world map chart:
#worldmap_chart.add('Countries', dict(countriesData))
for data in countriesData:
countryFound = [x["name"] for x in all_countries if x["id"].lower() == data[0]]
if (len(countryFound) > 0):
countryFound = countryFound[0]
worldmap_chart.add(countryFound, [{"value": data[0], "label" : (str(data[1]) + " video" + ("s" if data[1] != 1 else ""))}])
js_strings.append('<desc class="value">{0}: 1</desc>'.format(countryFound))
# Save chart as svg - comment this line for print the chart in this notebook:
worldmap_chart.render_to_file('chart_final_4.svg',
show_legend=True,
human_readable=True,
fill=True,
print_labels=True,
no_data_text="No data available",
legend_at_bottom=True,
pretty_print=True,
x_title="Hover the cursor on the country's name bellow or the map above:",
print_values=True)
print("Chart saved in local storage - see file (chart_final_4.svg).")
# Render the chart in the notebook:
HTML_worldChart = str(worldmap_chart.render(show_legend=True, human_readable=True,
fill=True, print_labels=True,
no_data_text="No data available",
legend_at_bottom=True,
print_values=True, style=DefaultStyle(value_font_family='googlefont:Raleway',
value_font_size=30,
value_colors=('white',))))
# Source: https://stackoverflow.com/a/62008698/12511801
from pathlib import Path
file = Path("chart_final_4.svg")
# Replace specific, conflicting countries by name:
file.write_text(file.read_text().replace('<desc class="value">Korea, Republic of: 1</desc>', '<desc class="value"></desc>'))
file.write_text(file.read_text().replace('<desc class="value">Russian Federation: 1</desc>', '<desc class="value"></desc>'))
file.write_text(file.read_text().replace('<desc class="value">Viet Nam: 1</desc>', '<desc class="value"></desc>'))
## This is due those countries are arranged as follows:
##--country (<desc class="value">Russia: 1</desc>) no detected
##--country (<desc class="value">Vietnam: 1</desc>) no detected
##--country (<desc class="value">South Korea: 1</desc>) no detected
# Loop the strings to replace:
for country in js_strings:
file.write_text(file.read_text().replace(country, '<desc class="value"></desc>'))
print("(chart_final_4.svg) has been modified.")
Output: world map chart modified - I'm modified the screenshot for include two tooltips at the same time.
Side note: it might not be the best way, but, for a lack of time and knowledge, I've decided to leave it as is. I'm open to any, better suggestions.

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'].

Folium markers with numbers inside

I would like to make some markers on the map using Folium with some numbers inside the markers.
The markers I want to look like the standard inverted drop shapes that Google Maps is using.
I saw that for folium.Marker you can use the parameter icon=folium.DivIcon("html code here"), but I don't know how I could make the html code so the marker looks like an inverted drop standard marker.
I saw a nice example here, using leaflet.js: https://github.com/iatkin/leaflet-svgicon, and here is their demo: http://iatkin.github.io/leaflet-svgicon/.
I just don't know how to implement that in Python.
The numbers that I would like to place inside the markers are some kind of rating numbers that have 3 characters (1 digit, a dot, and a decimal), for example 3.4, 4.8, 2.5, etc. the values are from 0.0 to 5.0.
This type of marker shape is fine too:
I saw some other solutions like this example, that show circles and showing the numbers inside, but it doesn't looks as nice as the regular pointy pushpin kinf of marker:
Numbers in map marker in Folium
Thanks,
Steven
Hello I did something similar, the result is the following:
The code is:
import folium
import folium.plugins as plugins
# BASE MAP
n = folium.Map(location=[20.299172882895903,-103.18423327532622], zoom_start = 10)
for i in range(0,len(DF)):
html=f"""
<h2> InformaciĆ³n de la ruta </h2>
<p> Ruta: {DF.iloc[i]['route_id']} </p>
<p> Secuencia de visita: {DF.iloc[i]['sequencia']} </p>
"""
iframe = folium.IFrame(html=html, width=200, height=150)
popup = folium.Popup(iframe, max_width=650)
folium.Marker(
location=[DF.iloc[i]['latitude'], DF.iloc[i]['longitude']], popup=popup,
icon=plugins.BeautifyIcon(
icon="arrow-down", icon_shape="marker",
number=DF.iloc[i]['sequencia'],
border_color= itinerario_lunes.iloc[i]['hex_code'],
background_color=itinerario_lunes.iloc[i]['hex_code']
)
).add_to(n)
# Show the map again
n

Categories

Resources