I am quite new to coding, python and pyside6. I have spent a few months getting the code below which seems to work well.
I have tried many different ways to achieve something quite simple (I think). I want to get access to the dataframe named self.filtered_df which is in the PandasModel class. I want this access to be in the def display_filtered_map(self): which is in the MainWindow class.
This is the full code
from PySide6.QtWidgets import QMainWindow, QApplication, QTableView, QLineEdit, QVBoxLayout, QWidget
from PySide6.QtCore import QAbstractTableModel, Qt, QModelIndex
from PySide6.QtWebEngineWidgets import QWebEngineView
import sys
import os
import sqlite3
import pandas as pd
import folium
from folium.plugins import FastMarkerCluster
from folium import LatLngPopup
from jinja2 import Template
from ui.vrpsselect_window import Ui_mw_Main
# code snippet from here: https://stackoverflow.com/questions/73481046/how-to-overide-a-feature-method-in-folium
class GetLatLngPopup(LatLngPopup):
_template = Template(u"""
{% macro script(this, kwargs) %}
var {{this.get_name()}} = L.popup();
function latLngPop(e) {
{{this.get_name()}}
.setLatLng(e.latlng)
.setContent("Latitude: " + e.latlng.lat.toFixed(6) +
"<br>Longitude: " + e.latlng.lng.toFixed(6))
.openOn({{this._parent.get_name()}});
}
{{this._parent.get_name()}}.on('click', latLngPop);
{% endmacro %}
""")
def __init__(self):
super(GetLatLngPopup, self).__init__()
self._name = 'GetLatLngPopup'
class PandasModel(QAbstractTableModel):
def __init__(self, dataframe: pd.DataFrame, parent=None):
super().__init__(parent)
self._dataframe = dataframe
self.filtered_df = dataframe.copy() # Initialize filtered DataFrame
def rowCount(self, parent=QModelIndex()) -> int:
if parent == QModelIndex():
return len(self.filtered_df)
return 0
def columnCount(self, parent=QModelIndex()) -> int:
if parent == QModelIndex():
return len(self.filtered_df.columns)
return 0
def data(self, index: QModelIndex, role=Qt.ItemDataRole):
if not index.isValid():
return None
if role == Qt.DisplayRole:
return str(self.filtered_df.iloc[index.row(), index.column()])
return None
def headerData(self, section: int, orientation: Qt.Orientation, role: Qt.ItemDataRole):
if role == Qt.DisplayRole:
if orientation == Qt.Horizontal:
return str(self.filtered_df.columns[section])
if orientation == Qt.Vertical:
return str(self.filtered_df.index[section])
return None
def filter_data(self, country_text: str, name_text: str, ident_text: str, airport_text: str):
country_filter = self._dataframe['Country'].str.contains(country_text, case=False)
name_filter = self._dataframe['Name'].str.contains(name_text, case=False)
ident_filter = self._dataframe['Ident'].str.contains(ident_text, case=False)
airport_filter = self._dataframe['Airport'].str.contains(airport_text, case=False)
self.filtered_df = self._dataframe[country_filter & name_filter & ident_filter & airport_filter]
self.layoutChanged.emit() # Update the view
class MainWindow(QMainWindow, Ui_mw_Main):
def __init__(self):
super().__init__()
self.setupUi(self)
# run on startup
self.setup()
self.load_database()
model = PandasModel(self.df)
self.tv_RP.setModel(model)
self.initialise()
# signals
self.pb_Close.clicked.connect(self.close)
self.le_SearchCountry.textChanged.connect(lambda text: model.filter_data(text, self.le_SearchName.text(), self.le_SearchIdent.text(), self.le_SearchAirport.text()))
self.le_SearchName.textChanged.connect(lambda text: model.filter_data(self.le_SearchCountry.text(), text, self.le_SearchIdent.text(), self.le_SearchAirport.text()))
self.le_SearchIdent.textChanged.connect(lambda text: model.filter_data(self.le_SearchCountry.text(), self.le_SearchName.text(), text, self.le_SearchAirport.text()))
self.le_SearchAirport.textChanged.connect(lambda text: model.filter_data(self.le_SearchCountry.text(), self.le_SearchName.text(), self.le_SearchIdent.text(), text))
self.pb_ResetSearches.clicked.connect(self.reset_searches)
self.tv_RP.clicked.connect(self.get_row_data)
def setup(self):
# connect to the database and create a cursor
conn = sqlite3.connect('data/VRPsS10.db')
c = conn.cursor()
# create a table VRPs
c.execute("""CREATE TABLE if not exists VRPs(
Country text,
Name text,
Ident text,
Airport text,
Source text,
Cycle text,
Elevation text,
Lat_DD text,
Lon_DD text,
Lat_DMS text,
Lon_DMS text)""")
# create a table Updates
c.execute("""CREATE TABLE if not exists Updates(
Effective text,
Cycle text)""")
# commit the changes and close connection
conn.commit()
conn.close()
def load_database(self):
# connect to the database and create a cursor
conn = sqlite3.connect('data/VRPsS10.db')
# read from the 'Updates' table
query = 'SELECT * FROM Updates'
self.df_update = pd.read_sql_query(query, conn)
self.setWindowTitle(f'AIRAC: {self.df_update.iat[0, 1]} Date: {self.df_update.iat[0, 0]}')
del [[self.df_update]]
# read from the 'VRPs' table
query = 'SELECT * FROM VRPs'
self.df = pd.read_sql_query(query, conn)
def initialise(self):
self.tv_RP.setColumnWidth(0, 60)
self.tv_RP.setColumnWidth(1, 230)
self.tv_RP.setColumnWidth(2, 60)
self.tv_RP.setColumnWidth(3, 105)
self.tv_RP.setColumnWidth(4, 60)
self.tv_RP.setColumnWidth(5, 60)
self.tv_RP.setColumnWidth(6, 70)
self.tv_RP.setColumnWidth(7, 80)
self.tv_RP.setColumnWidth(8, 80)
self.tv_RP.setColumnWidth(9, 110)
self.tv_RP.setColumnWidth(10, 110)
self.tv_RP.horizontalHeader().setDefaultAlignment(Qt.AlignmentFlag.AlignLeft)
self.tv_RP.setAlternatingRowColors(True)
# load map
self.webView = QWebEngineView()
location = (50.81506933272997, -1.203825772101116)
self.display_map(location)
def reset_searches(self):
self.le_SearchCountry.setText("")
self.le_SearchName.setText("")
self.le_SearchIdent.setText("")
self.le_SearchAirport.setText("")
def get_row_data(self, item):
location = (float(self.df.iloc[item.row(), self.df.columns.get_loc("Lat_DD")]),
float(self.df.iloc[item.row(), self.df.columns.get_loc("Lon_DD")]))
# display the map with this location
self.display_map(location)
def display_map(self, location):
# delete current webview
self.webView.deleteLater()
# create a separate map dataframe
df_map = self.df.copy()
df_map['Popup'] = "Country: " + df_map['Country'] + "<br>" + \
"Name: " + df_map['Name'] + "<br>" + \
"Ident: " + df_map['Ident'] + "<br>" + \
"Elevation: " + df_map['Elevation'] + " feet" + "<br>" + \
"Position DD: " + df_map['Lat_DD'] + ", " + df_map['Lon_DD'] + "<br>" + \
"Position DMS: " + df_map['Lat_DMS'] + ", " + df_map['Lon_DMS']
# change columns to float
df_map['Lat_DD'] = df_map['Lat_DD'].astype(float)
df_map['Lon_DD'] = df_map['Lon_DD'].astype(float)
# set up icons and markers
vrp_map = folium.Map(location=location, zoom_start=12, control_scale=True)
vrp_map.add_child(GetLatLngPopup())
callback = ('function (row) {'
'var marker = L.marker(new L.LatLng(row[0], row[1]), {color: "red"});'
'var icon = L.AwesomeMarkers.icon({'
"icon: 'info-sign',"
"iconColor: 'white',"
"markerColor: 'blue',"
"prefix: 'glyphicon',"
"extraClasses: 'fa-rotate-0'"
'});'
'marker.setIcon(icon);'
"var popup = L.popup({maxWidth: '300'});"
"const display_text = {text: row[2]};"
"var my_text = $(`<div id='my_text' class='display_text' style='width: 100.0%; height: 100.0%;'> "
"${display_text.text}</div>`)[0];"
"popup.setContent(my_text);"
"marker.bindPopup(popup);"
'return marker};')
vrp_map.add_child(FastMarkerCluster(df_map[['Lat_DD', 'Lon_DD', 'Popup']].values.tolist(), callback=callback))
# display map
self.webView = QWebEngineView()
self.webView.setHtml(vrp_map.get_root().render())
self.vl_Map.addWidget(self.webView)
# destroy map dataframe
del [[df_map]]
def display_filtered_map(self):
print(self.filtered_df)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
I have tried so many different ways to achieve this that I am now totally confused, but none have got me where I want to go.
I'm grateful for any help you can offer. Thanks in advance
The two following changes are necessary:
First, in the init of MainWindow, you need the get the instance of PandasModel visible to the whole class:
Second, when you want the filtered_df from the PandasModel instance, you have to specify the instance (here self.model):