Please revise and upgrade this code. import osmnx as ox
import simplekml
import tkinter as tk
from tkinter import ttk, messagebox
import logging
import os
import json
import networkx as nx
from shapely.geometry import Point, MultiPoint, Polygon, MultiPolygon
from geopy.distance import geodesic
import geopandas as gpd
from shapely.geometry import LineString
import random

# Set up logging configuration
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')

class NetworkDesignTool:
    def __init__(self, root):
        self.setup_ui(root)
        self.load_preferences()
        self.network_costs = {
            "fiber_cost_per_meter": 2.5,
            "trenching_cost_per_meter": 5.0,
            "manhole_cost": 500
        }
        self.graph = None
        self.buildings_gdf = None
        self.olt_location = None
        self.splitters = None
        self.customer_locations = None

    def setup_ui(self, root):
        """Setup the user interface components."""
        root.title("Advanced GPON FTTH Network Design Tool")
        
        self.create_label_entry(root, "Area Name:", 0)
        self.area_name_entry = self.entries[-1]

        self.create_label_combobox(root, "Fiber Ratio:", ["1:4", "1:8", "1:16", "1:32"], 1)
        self.fiber_ratio_combobox = self.comboboxes[-1]

        self.create_label_combobox(root, "Deployment Option:", ["underground", "overhead", "hybrid"], 2)
        self.deployment_option_combobox = self.comboboxes[-1]

        self.create_label_entry(root, "Fiber Cost (per meter):", 3, "2.5")
        self.fiber_cost_entry = self.entries[-1]
        self.create_label_entry(root, "Trenching Cost (per meter):", 4, "5.0")
        self.trenching_cost_entry = self.entries[-1]

        self.save_pref_var = tk.BooleanVar()
        tk.Checkbutton(root, text="Save Preferences", variable=self.save_pref_var).grid(row=5, column=0, padx=10, pady=5, columnspan=2)

        tk.Button(root, text="Run Network Design", command=self.run_network_design).grid(row=6, column=0, columnspan=2, pady=20)

        self.progress = ttk.Progressbar(root, orient='horizontal', length=250, mode='determinate')
        self.progress.grid(row=7, column=0, columnspan=2, pady=10)

        self.log_output = tk.Text(root, height=12, width=60)
        self.log_output.grid(row=8, column=0, columnspan=2, padx=10, pady=10)
        self.log_output.config(state=tk.DISABLED)

    def create_label_entry(self, root, label_text, row, default_val=""):
        tk.Label(root, text=label_text).grid(row=row, column=0, padx=10, pady=5, sticky='e')
        entry = tk.Entry(root, width=30)
        entry.grid(row=row, column=1, padx=10, pady=5)
        if default_val:
            entry.insert(0, default_val)
        if not hasattr(self, 'entries'):
            self.entries = []
        self.entries.append(entry)

    def create_label_combobox(self, root, label_text, values, row):
        tk.Label(root, text=label_text).grid(row=row, column=0, padx=10, pady=5, sticky='e')
        combobox = ttk.Combobox(root, values=values, state="readonly")
        combobox.grid(row=row, column=1, padx=10, pady=5)
        combobox.current(0)
        if not hasattr(self, 'comboboxes'):
            self.comboboxes = []
        self.comboboxes.append(combobox)

    def load_preferences(self):
        filepath = "user_preferences.json"
        if os.path.exists(filepath):
            try:
                with open(filepath, "r") as file:
                    preferences = json.load(file)
                    self.area_name_entry.insert(0, preferences.get("area_name", ""))
                    self.fiber_ratio_combobox.set(preferences.get("fiber_ratio", "1:4"))
                    self.deployment_option_combobox.set(preferences.get("deployment_option", "underground"))
            except json.JSONDecodeError as e:
                self.log_message(f"Error loading preferences: {str(e)}", level="error")

    def save_preferences(self):
        preferences = {
            "area_name": self.area_name_entry.get(),
            "fiber_ratio": self.fiber_ratio_combobox.get(),
            "deployment_option": self.deployment_option_combobox.get(),
        }
        with open("user_preferences.json", "w") as file:
            json.dump(preferences, file, indent=4)

    def init_logging(self):
        self.logger = logging.getLogger('NetworkDesignTool')
        fh = logging.FileHandler('network_design.log')
        fh.setLevel(logging.DEBUG)
        self.logger.addHandler(fh)

    def log_message(self, message, level="info"):
        self.logger.log(getattr(logging, level.upper()), message)
        self.log_output.config(state=tk.NORMAL)
        self.log_output.insert(tk.END, f"{message}\n")
        self.log_output.config(state=tk.DISABLED)
        self.log_output.see(tk.END)

    def retrieve_data(self, area_name):
        self.log_message(f"Retrieving data for {area_name}...")
        try:
            self.graph = ox.graph_from_place(area_name, network_type='drive')
            self.buildings_gdf = ox.geometries_from_place(area_name, tags={'building': True})
            self.log_message(f"Nodes in graph: {len(self.graph.nodes)}, Buildings: {len(self.buildings_gdf)}")
            return self.graph
        except Exception as e:
            self.log_message(f"Error retrieving data: {str(e)}", level="error")
            return None

    def design_network(self, graph, fiber_ratio):
        self.log_message("Designing GPON FTTH network...")
        largest_connected_graph = self.get_largest_connected_component(graph)
        OLT_location = random.choice(list(largest_connected_graph.nodes()))
        olt_coords = (graph.nodes[OLT_location]['y'], graph.nodes[OLT_location]['x'])

        customer_locations = [
            building.geometry.centroid 
            for idx, building in self.buildings_gdf.iterrows()
            if isinstance(building.geometry, (Point, MultiPoint, Polygon, MultiPolygon))
        ]

        splitters = [
            customer_locations[i] for i in range(0, len(customer_locations), int(fiber_ratio.split(':')[1]))
        ]

        self.log_message(f"OLT placed at {olt_coords}, {len(customer_locations)} customer locations identified.")
        return olt_coords, splitters, customer_locations

    def get_largest_connected_component(self, graph):
        if nx.is_empty(graph):
            return graph
        undirected_graph = graph.to_undirected()
        largest_cc = max(nx.connected_components(undirected_graph), key=len)
        return graph.subgraph(largest_cc).copy()

    def calculate_paths(self, olt_location, customer_locations):
        paths = []
        try:
            for customer in customer_locations:
                olt_node = ox.distance.nearest_nodes(self.graph, olt_location[1], olt_location[0])
                customer_node = ox.distance.nearest_nodes(self.graph, customer.x, customer.y)
                if nx.has_path(self.graph, olt_node, customer_node):
                    path = nx.shortest_path(self.graph, olt_node, customer_node, weight='length')
                    paths.append(path)
                else:
                    self.log_message(f"No path between OLT and customer at {customer}", level="warning")
            self.log_message(f"Calculated paths for {len(paths)} customers.")
        except Exception as e:
            self.log_message(f"Error calculating paths: {str(e)}", level="error")
        return paths

    def calculate_total_cost(self, paths):
        total_cost = 0.0
        fiber_cost_per_meter = float(self.fiber_cost_entry.get())
        trenching_cost_per_meter = float(self.trenching_cost_entry.get())

        try:
            for path in paths:
                length = sum(
                    geodesic(
                        (self.graph.nodes[u]['y'], self.graph.nodes[u]['x']), 
                        (self.graph.nodes[v]['y'], self.graph.nodes[v]['x'])
                    ).meters for u, v in zip(path[:-1], path[1:])
                )
                total_cost += length * (fiber_cost_per_meter + trenching_cost_per_meter)

            total_cost += len(paths) * self.network_costs['manhole_cost']
            self.log_message(f"Estimated total cost: ${total_cost:.2f}")
        except Exception as e:
            self.log_message(f"Error calculating total cost: {str(e)}", level="error")
        return total_cost

    def generate_kml(self, olt_location, paths):
        self.log_message("Generating KML file...")
        try:
            kml = simplekml.Kml()

            # OLT point
            kml.newpoint(name="OLT", coords=[(olt_location[1], olt_location[0])])

            # Lines to customers
            for i, path in enumerate(paths):
                coords = [(self.graph.nodes[node]['x'], self.graph.nodes[node]['y']) for node in path]
                kml.newlinestring(name=f"Path {i+1}", coords=coords)

            kml_file_path = "network_design.kml"
            kml.save(kml_file_path)
            self.log_message(f"KML file generated at {kml_file_path}.")
        except Exception as e:
            self.log_message(f"Error generating KML: {str(e)}", level="error")

    def generate_shapefile(self, olt_location, paths):
        self.log_message("Generating Shapefile...")
        try:
            records = []

            for i, path in enumerate(paths):
                line_coords = [(self.graph.nodes[node]['x'], self.graph.nodes[node]['y']) for node in path]
                line_geom = LineString(line_coords)
                records.append({"geometry": line_geom, "OLT": olt_location, "path_id": f"Path_{i+1}"})

            gdf = gpd.GeoDataFrame(records, crs='EPSG:4326', geometry='geometry')
            shapefile_path = "network_design.shp"
            gdf.to_file(shapefile_path, driver='ESRI Shapefile')
            self.log_message(f"Shapefile generated at {shapefile_path}.")
        except Exception as e:
            self.log_message(f"Error generating shapefile: {str(e)}", level="error")

    def run_network_design(self):
        area_name = self.area_name_entry.get()
        fiber_ratio = self.fiber_ratio_combobox.get()
        
        self.log_message("Starting network design process...")
        self.progress.start()

        # Retrieve data
        graph = self.retrieve_data(area_name)
        if graph is None:
            self.progress.stop()
            return
        
        # Design network
        self.olt_location, self.splitters, self.customer_locations = self.design_network(graph, fiber_ratio)
        if self.olt_location is None:
            self.progress.stop()
            return
        
        # Calculate paths
        paths = self.calculate_paths(self.olt_location, self.customer_locations)

        # Calculate total cost
        total_cost = self.calculate_total_cost(paths)

        # Generate KML and Shapefile
        self.generate_kml(self.olt_location, paths)
        self.generate_shapefile(self.olt_location, paths)

        if self.save_pref_var.get():
            self.save_preferences()

        self.progress.stop()
        self.log_message("Network design process completed.")

if __name__ == "__main__":
    root = tk.Tk()
    app = NetworkDesignTool(root)
    root.mainloop()