OpenLayer显示地图(控件、标注、漫游事件)

该文章展示了一个使用OpenLayers库创建的Web地图应用,加载了高德地图作为底图,并从GeoJSON数据源加载矢量数据。用户可以点击地图添加点,且有动画效果的漫游功能。此外,还包括了放大缩小、全屏和跳转范围的控件。
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!--  -->
    <!-- 引入ol(OpenLayer)css和js资源-->
    <link href="https://cdn.bootcdn.net/ajax/libs/ol3/4.6.5/ol.css" rel="stylesheet">
    <script src="https://cdn.bootcdn.net/ajax/libs/ol3/4.6.5/ol.js"></script>
    <style>
        html,
        body {
            margin: 0;
            height: 100%;
        }

        #map {
            position: absolute;
            top: 0;
            bottom: 0;
            width: 100%;
        }

        /* 修改控件位置 */
        .ol-zoomslider {
            top: 7.5em;


        }

        .btn {
            position: fixed;
            top: 60px;
            right: 20px;
        }
    </style>
</head>

<body>
    <div id="map"></div>
    <button class="btn">复位地图</button>
</body>
<script>
    const gaode = new ol.layer.Tile({
        title: '高德地图',
        source: new ol.source.XYZ({
            url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&style=7&x={x}&y={y}&z={z}',
            wrapX: false
        })
    })
    //1. 创建地图
    const map = new ol.Map({
        // 挂载到Dom元素上
        target: 'map',
        // 添加图层
        layers: [gaode],
        // 设置视图
        view: new ol.View({
            center: [114.30, 30.50],
            zoom: 14,
            projection: 'EPSG:4326'
        })
    })

    //2. 将数据添加到矢量数据源中
    var source = new ol.source.Vector({
        url: "./data/map.json",
        format: new ol.format.GeoJSON()
    })
    // 3设置资源到图层
    var layer = new ol.layer.Vector({
        source
    })
    // 4.设置图层到地图
    map.addLayer(layer)
    // 5设置点的样式
    var style = new ol.style.Style({
        image: new ol.style.Circle({
            radius: 20,
            fill: new ol.style.Fill({
                color: "#ffed66"
            }),
            stroke: new ol.style.Stroke({
                color: "#333",
                width: 3
            })
        })
    })
    layer.setStyle(style)
    // 6.添加控件
    // 添加跳转控件
    const zoomtoExtent = new ol.control.ZoomToExtent({
        extent: [110, 30, 160, 30],

    })
    map.addControl(zoomtoExtent)

    // 添加放大缩小控件
    const zoomSlider = new ol.control.ZoomSlider()
    map.addControl(zoomSlider)

    // 添加全屏控件
    const full = new ol.control.FullScreen()
    map.addControl(full)
    //7、 添加点击事件
    map.on('click', function (e) {
        var {
            coordinate
        } = e
        //添加要素
        var point = new ol.Feature({
            geometry: new ol.geom.Point(coordinate)
        })
        source.addFeature(point)
        // 实现漫游
        var view = map.getView()
        view.animate({
            center: coordinate
        })
    })
    //8. 点击漫游复位
    var btn = document.querySelector('.btn')
    btn.onclick = function () {
        map.getView().animate({
            center: [114.30, 30.50],
            zoom: 6,
            duration: 3000
        })
    }
</script>

</html>

data-->map.json 数据

{
    "type": "FeatureCollection",
    "features": [
        {
            "type": "Feature",
            "geometry": {
                "type": "Point",
                "coordinates": [114.30, 30.50]
            }
        }
    ]
}

好吧,我将给出我的原始代码,请你结合你的修改代码进行修改。原始代码如下:from collections import defaultdict import tkinter as tk from tkinter import ttk, messagebox, filedialog, scrolledtext import numpy as np import random import json from datetime import datetime from PIL import Image, ImageTk import os import math from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg import matplotlib.pyplot as plt import matplotlib.colors as mcolors import matplotlib.patches as patches class ContainerYard: def __init__(self, rows=15, cols=20, max_height=4): self.rows = rows self.cols = cols self.max_height = max_height self.yard = np.zeros((rows, cols, max_height), dtype=object) self.stacking_records = [] self.collapse_records = [] self.exceptions = [] # 集装箱分类区域划分 - 用于不同货物类型 self.type_zones = { "电子产品": (0, 0, rows//3, cols//3), "化工原料": (rows//3, 0, 2*rows//3, cols//3), "生鲜食品": (0, cols//3, rows//3, 2*cols//3), "机械设备": (rows//3, cols//3, 2*rows//3, 2*cols//3), "纺织品": (0, 2*cols//3, rows//3, cols), "其它": (2*rows//3, 0, rows, cols) } # 泊位分配(在洋山港地图对应区域) self.berths = { "泊位1": (2*rows//3, 2*cols//3, rows, cols), "泊位2": (2*rows//3, cols//3, rows, 2*cols//3), "泊位3": (2*rows//3, 0, rows, cols//3), "泊位4": (0, 0, rows//3, cols//3) } def get_position_score(self, row, col, c_type): """计算位置得分,考虑货物类型和离泊位的距离""" # 获取当前货物类型的区域 top, left, bottom, right = self.type_zones.get(c_type, (0, 0, self.rows, self.cols)) # 检查是否在区域内 in_zone = top <= row < bottom and left <= col < right zone_score = 1.0 if in_zone else 0.7 # 计算到中心点的距离(靠近中心的得分更高) center_row = (top + bottom) / 2 center_col = (left + right) / 2 distance = math.sqrt((row - center_row)**2 + (col - center_col)**2) max_distance = math.sqrt(self.rows**2 + self.cols**2) distance_score = 1.0 - distance / max_distance # 组合得分 return zone_score * 0.6 + distance_score * 0.4 def initial_stacking(self, containers): """初始堆叠算法,考虑货物类型的指定区域""" if not containers: return # 按货物类型分类容器 type_containers = defaultdict(list) for container in containers: type_containers[container['type']].append(container) # 分配堆叠位置 for c_type, c_list in type_containers.items(): # 找出该类型可用的堆叠位置 available_spots = [] for row in range(self.rows): for col in range(self.cols): height = self.get_current_height(row, col) if height < self.max_height: score = self.get_position_score(row, col, c_type) available_spots.append((row, col, height, score)) # 按位置得分排序 available_spots.sort(key=lambda x: x[3], reverse=True) # 为每个容器分配位置 for i, container in enumerate(c_list): if i >= len(available_spots): # 没有可用位置了,记录异常 self.exceptions.append({ 'type': '爆仓', 'container': container, 'time': datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 'message': f"货物类型 {c_type} 没有足够空间存放" }) continue row, col, height, _ = available_spots[i] # 堆叠容器 self.yard[row, col, height] = container container['position'] = (row, col, height) container['stack_time'] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") # 记录堆叠操作 self.stacking_records.append({ 'operation': 'stack', 'container': container.copy(), 'time': container['stack_time'] }) def collapse_stack(self, row, col, height): """封顶塌陷算法""" collapse_count = 0 collapse_list = [] # 从指定位置上方开始塌陷 for h in range(height, self.max_height): if self.yard[row, col, h] is not None and self.yard[row, col, h] != 0: container = self.yard[row, col, h] collapse_list.append(container) self.yard[row, col, h] = None collapse_count += 1 # 重新堆叠塌陷的容器 if collapse_list: self.stacking_records.append({ 'operation': 'collapse', 'position': (row, col), 'collapsed_containers': [c['id'] for c in collapse_list], 'time': datetime.now().strftime("%Y-%m-%d %H:%M:%S") }) # 更新塌陷记录 for container in collapse_list: self.collapse_records.append({ 'container_id': container['id'], 'original_position': (row, col, container['position'][2]), 'time': datetime.now().strftime("%Y-%m-%d %H:%M:%S") }) # 重新堆叠塌陷的容器 self.initial_stacking(collapse_list) return collapse_count def get_current_height(self, row, col): """获取当前堆叠高度""" for height in range(self.max_height): if self.yard[row, col, height] is None or self.yard[row, col, height] == 0: return height return self.max_height def get_yard_status(self, layer=None): """获取堆场状态,可指定层""" if layer is None: status = [] for row in range(self.rows): row_status = [] for col in range(self.cols): height = self.get_current_height(row, col) row_status.append(height) status.append(row_status) return status else: # 获取特定层的信息 layer_status = [] for row in range(self.rows): row_status = [] for col in range(self.cols): if layer < self.get_current_height(row, col): container = self.yard[row, col, layer] row_status.append(container['type'] if container else "空") else: row_status.append("空") layer_status.append(row_status) return layer_status def get_yard_capacity(self): """获取堆场容量表""" capacity = [] for row in range(self.rows): for col in range(self.cols): height = self.get_current_height(row, col) types = [] for h in range(height): if self.yard[row, col, h]: types.append(self.yard[row, col, h]['type']) capacity.append({ 'row': row, 'col': col, 'current_height': height, 'max_height': self.max_height, 'types': types, 'utilization': f"{height}/{self.max_height} ({height/self.max_height*100:.1f}%)" }) return capacity class ContainerInOutSystem: def __init__(self, root): self.root = root self.root.title("洋山港集装箱入库管理系统") self.root.geometry("1280x800") # 初始化数据 self.container_count = 0 self.container_yard = ContainerYard(rows=15, cols=20, max_height=5) self.current_map = None self.map_photo = None self.containers_to_add = [] self.current_layer = 0 self.show_types = False self.map_scale = 1.0 self.map_offset_x = 0 self.map_offset_y = 0 self.map_drag_start = None # 创建界面 self.create_menu() self.create_main_interface() self.create_status_bar() def create_menu(self): """创建菜单栏""" menubar = tk.Menu(self.root) # 文件菜单 file_menu = tk.Menu(menubar, tearoff=0) file_menu.add_command(label="加载地图", command=self.load_map) file_menu.add_command(label="保存记录", command=self.save_records) file_menu.add_command(label="加载记录", command=self.load_records) file_menu.add_separator() file_menu.add_command(label="退出", command=self.root.quit) menubar.add_cascade(label="文件", menu=file_menu) # 视图菜单 view_menu = tk.Menu(menubar, tearoff=0) view_menu.add_command(label="堆场状态", command=self.show_yard_status) view_menu.add_command(label="类型分布", command=self.show_type_distribution) view_menu.add_command(label="堆层切换", command=self.switch_layer_dialog) menubar.add_cascade(label="视图", menu=view_menu) # 记录菜单 record_menu = tk.Menu(menubar, tearoff=0) record_menu.add_command(label="入库记录", command=self.show_inbound_records) record_menu.add_command(label="塌陷记录", command=self.show_collapse_records) record_menu.add_command(label="异常记录", command=self.show_exception_records) menubar.add_cascade(label="记录", menu=record_menu) # 工具菜单 tool_menu = tk.Menu(menubar, tearoff=0) tool_menu.add_command(label="堆场容量表", command=self.show_capacity_table) tool_menu.add_command(label="备忘录", command=self.show_memo_dialog) tool_menu.add_command(label="清除选择", command=self.clear_selections) menubar.add_cascade(label="工具", menu=tool_menu) self.root.config(menu=menubar) def create_main_interface(self): """创建主界面""" main_frame = ttk.Frame(self.root) main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) # 左侧地图区域 map_frame = ttk.LabelFrame(main_frame, text="洋山港电子地图") map_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5) # 地图导航工具栏 nav_frame = ttk.Frame(map_frame) nav_frame.pack(fill=tk.X, padx=5, pady=2) ttk.Button(nav_frame, text="放大", command=lambda: self.zoom_map(1.1)).pack(side=tk.LEFT, padx=2) ttk.Button(nav_frame, text="缩小", command=lambda: self.zoom_map(0.9)).pack(side=tk.LEFT, padx=2) ttk.Button(nav_frame, text="还原", command=self.reset_map_view).pack(side=tk.LEFT, padx=2) ttk.Button(nav_frame, text="漫游模式", command=self.toggle_drag_mode).pack(side=tk.LEFT, padx=10) ttk.Label(nav_frame, text="堆层:").pack(side=tk.LEFT, padx=5) self.layer_var = tk.IntVar(value=0) layer_combo = ttk.Combobox(nav_frame, textvariable=self.layer_var, width=5, values=list(range(self.container_yard.max_height))) layer_combo.pack(side=tk.LEFT, padx=2) layer_combo.bind("<<ComboboxSelected>>", self.change_layer) ttk.Checkbutton(nav_frame, text="显示货物类型", variable=tk.BooleanVar(value=self.show_types), command=self.toggle_show_types).pack(side=tk.LEFT, padx=10) # 地图显示区域 self.map_canvas = tk.Canvas(map_frame, bg='#e0e0e0', width=800, height=500) self.map_canvas.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) # 绑定地图事件 self.map_canvas.bind("<MouseWheel>", self.on_mouse_wheel) self.map_canvas.bind("<ButtonPress-1>", self.on_map_drag_start) self.map_canvas.bind("<B1-Motion>", self.on_map_drag) self.map_canvas.bind("<ButtonRelease-1>", self.on_map_drag_end) # 右侧控制面板 control_frame = ttk.Frame(main_frame) control_frame.pack(side=tk.RIGHT, fill=tk.Y, padx=5, pady=5) # 输入框部分 input_frame = ttk.LabelFrame(control_frame, text="集装箱信息") input_frame.pack(fill=tk.X, padx=5, pady=5) ttk.Label(input_frame, text="泊位:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5) self.berth_combo = ttk.Combobox(input_frame, values=list(self.container_yard.berths.keys()), width=18) self.berth_combo.grid(row=0, column=1, sticky=tk.W, padx=5, pady=5) self.berth_combo.current(0) ttk.Label(input_frame, text="货物类型:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=5) self.type_combo = ttk.Combobox(input_frame, values=list(self.container_yard.type_zones.keys()), width=18) self.type_combo.grid(row=1, column=1, sticky=tk.W, padx=5, pady=5) self.type_combo.current(0) ttk.Label(input_frame, text="货物数量:").grid(row=2, column=0, sticky=tk.W, padx=5, pady=5) self.count_entry = ttk.Entry(input_frame, width=20) self.count_entry.grid(row=2, column=1, sticky=tk.W, padx=5, pady=5) self.count_entry.insert(0, "1") ttk.Label(input_frame, text="储存时间():").grid(row=3, column=0, sticky=tk.W, padx=5, pady=5) self.duration_entry = ttk.Entry(input_frame, width=20) self.duration_entry.grid(row=3, column=1, sticky=tk.W, padx=5, pady=5) self.duration_entry.insert(0, "30") # 分配按钮 self.assign_btn = ttk.Button(control_frame, text="开始分配", command=self.assign_containers) self.assign_btn.pack(fill=tk.X, padx=5, pady=10) # 小地图区域 mini_map_frame = ttk.LabelFrame(control_frame, text="堆场概览") mini_map_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) self.mini_map_figure = plt.Figure(figsize=(3, 3), dpi=80) self.mini_map_canvas = FigureCanvasTkAgg(self.mini_map_figure, mini_map_frame) self.mini_map_canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True, padx=5, pady=5) ax = self.mini_map_figure.add_subplot(111) ax.set_title("堆场概览") self.update_mini_map(ax) # 日志框 log_frame = ttk.LabelFrame(control_frame, text="操作日志") log_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) self.log_text = scrolledtext.ScrolledText(log_frame, height=10, width=40) self.log_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) self.log_text.config(state=tk.DISABLED) def create_status_bar(self): """创建状态栏""" self.status_bar = ttk.Frame(self.root) self.status_bar.pack(side=tk.BOTTOM, fill=tk.X, padx=10, pady=5) self.status_label = ttk.Label(self.status_bar, text="就绪 | 比例: 100% | 堆层: 0 | 位置: (0, 0)") self.status_label.pack(side=tk.LEFT) self.container_count_label = ttk.Label(self.status_bar, text="集装箱总数: 0") self.container_count_label.pack(side=tk.RIGHT, padx=10) def update_status(self, message): """更新状态栏""" self.status_label.config(text=f"{message} | 比例: {self.map_scale*100:.0f}% | 堆层: {self.current_layer} | 位置: ({self.map_offset_x}, {self.map_offset_y})") self.status_label.update_idletasks() def log_message(self, message): """记录日志消息""" self.log_text.config(state=tk.NORMAL) timestamp = datetime.now().strftime("%H:%M:%S") self.log_text.insert(tk.END, f"[{timestamp}] {message}\n") self.log_text.config(state=tk.DISABLED) self.log_text.see(tk.END) self.update_status(message) def load_map(self): """加载洋山港地图数据""" filetypes = [("地图文件", "*.png *.jpg *.jpeg"), ("所有文件", "*.*")] filename = filedialog.askopenfilename(title="选择洋山港地图文件", filetypes=filetypes) if filename: try: # 加载地图图片 self.current_map = Image.open(filename) self.map_photo = ImageTk.PhotoImage(self.current_map) # 绘制地图 self.draw_map() # 绘制堆场区域和泊位 self.draw_yard_zones() self.log_message(f"地图加载成功: {os.path.basename(filename)}") self.log_message("堆场区域和泊位已标注") except Exception as e: messagebox.showerror("错误", f"加载地图失败: {str(e)}") self.log_message(f"地图加载失败: {str(e)}") def draw_map(self): """绘制地图到画布""" if not self.map_photo: return # 清空画布 self.map_canvas.delete("all") # 计算缩放和偏移 scaled_width = int(self.current_map.width * self.map_scale) scaled_height = int(self.current_map.height * self.map_scale) # 创建缩放后的图片 resized_img = self.current_map.resize((scaled_width, scaled_height), Image.LANCZOS) self.map_photo = ImageTk.PhotoImage(resized_img) # 绘制地图 self.map_canvas.create_image( self.map_offset_x, self.map_offset_y, anchor=tk.NW, image=self.map_photo, tags="map" ) # 更新堆场状态覆盖 self.draw_yard_overlay() def draw_yard_zones(self): """在堆场上绘制不同类型区域的边框""" if not self.map_photo: return # 获取地图的缩放比例 scale_x = self.map_canvas.winfo_width() / self.container_yard.cols scale_y = self.map_canvas.winfo_height() / self.container_yard.rows # 绘制货物类型区域 colors = { "电子产品": "blue", "化工原料": "green", "生鲜食品": "yellow", "机械设备": "orange", "纺织品": "purple", "其它": "gray" } for zone_name, (top, left, bottom, right) in self.container_yard.type_zones.items(): # 计算屏幕坐标 x1 = left * scale_x y1 = top * scale_y x2 = right * scale_x y2 = bottom * scale_y # 绘制矩形 self.map_canvas.create_rectangle( x1, y1, x2, y2, outline=colors[zone_name], width=2, dash=(4, 4), tags="zone" ) # 添加标签 self.map_canvas.create_text( (x1+x2)/2, (y1+y2)/2, text=zone_name, fill=colors[zone_name], font=("Arial", 10, "bold"), tags="zone" ) # 绘制泊位 berth_colors = ["red", "cyan", "magenta", "brown"] for i, (berth_name, (top, left, bottom, right)) in enumerate(self.container_yard.berths.items()): # 计算屏幕坐标 x1 = left * scale_x y1 = top * scale_y x2 = right * scale_x y2 = bottom * scale_y # 绘制矩形 self.map_canvas.create_rectangle( x1, y1, x2, y2, outline=berth_colors[i], width=3, tags="berth" ) # 添加泊位标签 self.map_canvas.create_text( (x1+x2)/2, (y1+y2)/2, text=berth_name, fill=berth_colors[i], font=("Arial", 12, "bold"), tags="berth" ) def draw_yard_overlay(self): """绘制堆场状态覆盖层,显示当前层集装箱位置""" if not self.map_photo: return # 获取地图的缩放比例 canvas_width = self.map_canvas.winfo_width() canvas_height = self.map_canvas.winfo_height() scale_x = canvas_width / self.container_yard.cols scale_y = canvas_height / self.container_yard.rows # 清除之前的覆盖层 self.map_canvas.delete("container") # 获取当前层状态 layer_status = self.container_yard.get_yard_status(self.current_layer) # 定义颜色映射 type_colors = { "电子产品": "blue", "化工原料": "green", "生鲜食品": "#FFC000", # 黄色 "机械设备": "orange", "纺织品": "purple", "其它": "gray", "空": "#E0E0E0" # 浅灰色 } # 绘制集装箱位置 for row in range(self.container_yard.rows): for col in range(self.container_yard.cols): if layer_status[row][col] != "空": # 计算屏幕坐标 x1 = col * scale_x y1 = row * scale_y x2 = (col+1) * scale_x y2 = (row+1) * scale_y # 绘制矩形 container_type = layer_status[row][col] fill_color = type_colors.get(container_type, "gray") self.map_canvas.create_rectangle( x1, y1, x2, y2, outline="black", fill=fill_color, width=1, stipple="gray25", # 半透明效果 tags="container" ) # 如果显示货物类型,添加文本标签 if self.show_types: self.map_canvas.create_text( (x1+x2)/2, (y1+y2)/2, text=container_type[:2], fill="black" if fill_color == "#FFC000" else "white", font=("Arial", 8), tags="container" ) def update_mini_map(self, ax): """更新小地图显示""" ax.clear() # 获取堆场状态 status = self.container_yard.get_yard_status() data = np.array(status) # 创建颜色映射 cmap = plt.cm.get_cmap('viridis') norm = mcolors.Normalize(vmin=0, vmax=self.container_yard.max_height) # 绘制热力图 im = ax.imshow(data, cmap=cmap, norm=norm, aspect='auto') # 添加标题和颜色条 ax.set_title("堆场利用率概览") plt.colorbar(im, ax=ax) self.mini_map_canvas.draw() def zoom_map(self, factor): """缩放地图""" if not self.current_map: return self.map_scale *= factor self.map_scale = max(0.1, min(self.map_scale, 3.0)) # 限制缩放范围 self.draw_map() self.log_message(f"地图缩放: {self.map_scale*100:.0f}%") def reset_map_view(self): """重置地图视图""" self.map_scale = 1.0 self.map_offset_x = 0 self.map_offset_y = 0 self.draw_map() self.log_message("地图视图已重置") def toggle_drag_mode(self): """切换漫游模式""" if self.map_canvas["cursor"] == "fleur": self.map_canvas.config(cursor="") self.log_message("漫游模式已关闭") else: self.map_canvas.config(cursor="fleur") self.log_message("漫游模式已开启") def on_mouse_wheel(self, event): """鼠标滚轮事件处理""" if not self.current_map: return factor = 1.1 if event.delta > 0 else 0.9 self.zoom_map(factor) def on_map_drag_start(self, event): """开始地图拖动""" self.map_drag_start = (event.x, event.y) def on_map_drag(self, event): """地图拖动中""" if self.map_drag_start and self.map_canvas["cursor"] == "fleur": dx = event.x - self.map_drag_start[0] dy = event.y - self.map_drag_start[1] self.map_offset_x += dx self.map_offset_y += dy self.map_drag_start = (event.x, event.y) self.draw_map() def on_map_drag_end(self, event): """结束地图拖动""" self.map_drag_start = None def change_layer(self, event=None): """改变当前显示的堆层""" self.current_layer = self.layer_var.get() self.draw_yard_overlay() self.log_message(f"切换到堆层: {self.current_layer}") def toggle_show_types(self): """切换是否显示货物类型""" self.show_types = not self.show_types self.draw_yard_overlay() state = "开启" if self.show_types else "关闭" self.log_message(f"货物类型显示已{state}") def clear_selections(self): """清除所有选择标记""" self.map_canvas.delete("selection") self.log_message("选择标记已清除") def validate_input(self): """验证输入数据""" errors = [] # 验证泊位 berth = self.berth_combo.get().strip() if not berth or berth not in self.container_yard.berths: errors.append("请选择有效的泊位") # 验证货物类型 c_type = self.type_combo.get().strip() if not c_type or c_type not in self.container_yard.type_zones: errors.append("请选择有效的货物类型") # 验证货物数量 count = self.count_entry.get().strip() if not count.isdigit() or int(count) <= 0 or int(count) > 100: errors.append("货物数量必须是1-100之间的整数") # 验证储存时间 duration = self.duration_entry.get().strip() if not duration.isdigit() or int(duration) <= 0 or int(duration) > 365: errors.append("储存时间必须是1-365之间的整数") return errors def assign_containers(self): """分配集装箱""" # 验证输入 errors = self.validate_input() if errors: messagebox.showwarning("输入错误", "\n".join(errors)) for error in errors: self.log_message(f"输入错误: {error}") return # 获取输入值 berth = self.berth_combo.get().strip() c_type = self.type_combo.get().strip() count = int(self.count_entry.get().strip()) duration = int(self.duration_entry.get().strip()) # 生成集装箱数据 containers = [] for i in range(count): self.container_count += 1 container = { 'id': f"C{self.container_count:04d}", 'berth': berth, 'type': c_type, 'size': random.choice(['20ft', '40ft']), 'weight': random.randint(3, 30), 'owner': random.choice(['公司A', '公司B', '公司C']), 'destination': random.choice(['上海', '深圳', '新加坡', '洛杉矶']), 'duration': duration, 'position': None, 'stack_time': None } containers.append(container) self.containers_to_add.append(container) # 更新UI self.log_message(f"已添加{count}个{c_type}集装箱到泊位{berth}") # 创建并开始分配过程 self.start_allocation_process(containers) def start_allocation_process(self, containers): """开始分配过程动画""" # 禁用按钮 self.assign_btn.config(state=tk.DISABLED) # 在后台进行分配 self.root.after(100, self.perform_allocation, containers) def perform_allocation(self, containers): """执行分配过程""" try: # 初始堆叠 self.log_message("正在进行初始堆叠...") self.container_yard.initial_stacking(containers) # 检查是否有爆仓异常 if self.container_yard.exceptions: for exception in self.container_yard.exceptions: self.log_message(f"异常: {exception['message']}") self.container_yard.exceptions.clear() # 更新小地图 ax = self.mini_map_figure.add_subplot(111) self.update_mini_map(ax) # 更新主地图 self.draw_map() self.draw_yard_overlay() # 更新统计信息 self.container_count_label.config(text=f"集装箱总数: {self.container_count}") # 显示成功消息 self.log_message(f"成功分配{len(containers)}个集装箱到堆场") # 记录完成操作 for container in containers: if container['position'] is not None: position = f"{container['position'][0]},{container['position'][1]},{container['position'][2]}" self.log_message(f"集装箱 {container['id']} 已堆叠在位置 {position}") # 启用按钮 self.assign_btn.config(state=tk.NORMAL) except Exception as e: self.log_message(f"分配过程中发生错误: {str(e)}") messagebox.showerror("分配错误", f"分配过程中发生错误: {str(e)}") self.assign_btn.config(state=tk.NORMAL) def show_yard_status(self): """显示堆场状态""" status = self.container_yard.get_yard_status() status_text = "堆场状态 (行/列):\n " + "\t".join(str(i) for i in range(self.container_yard.cols)) + "\n" for i, row in enumerate(status): status_text += f"{i:2d} [\t" + "\t".join(str(height) for height in row) + "]\n" self.show_info_dialog("堆场状态", status_text) def show_type_distribution(self): """显示货物类型分布""" type_distribution = self.container_yard.get_container_count_by_type() total = sum(type_distribution.values()) # 创建图表 fig = plt.Figure(figsize=(7, 5), dpi=80) ax = fig.add_subplot(111) types = list(type_distribution.keys()) counts = list(type_distribution.values()) if types: # 生成不同颜色 colors = plt.cm.tab10(range(len(types))) ax.bar(types, counts, color=colors) # 添加数据标签 for i, v in enumerate(counts): ax.text(i, v + 0.5, str(v), ha='center', fontsize=10) ax.set_title(f"货物类型分布 (总数: {total})") ax.set_ylabel("数量") ax.set_xlabel("货物类型") ax.tick_params(axis='x', rotation=15) else: ax.text(0.5, 0.5, "无货物数据", fontsize=15, ha='center', va='center', color='gray') ax.set_title("货物类型分布") # 在Tkinter中显示图表 self.show_figure_dialog("货物类型分布", fig) def switch_layer_dialog(self): """切换堆层对话框""" dialog = tk.Toplevel(self.root) dialog.title("堆层切换") dialog.geometry("300x200") ttk.Label(dialog, text="请选择要显示的堆层:").pack(pady=10) layer_var = tk.IntVar(value=self.current_layer) layer_combo = ttk.Combobox(dialog, textvariable=layer_var, width=10, values=list(range(self.container_yard.max_height))) layer_combo.pack(pady=10) layer_combo.current(self.current_layer) def apply_layer(): self.current_layer = layer_var.get() self.layer_var.set(self.current_layer) self.draw_yard_overlay() dialog.destroy() self.log_message(f"切换到堆层: {self.current_layer}") ttk.Button(dialog, text="确认", command=apply_layer).pack(pady=20) def show_inbound_records(self): """显示入库记录""" if not self.container_yard.stacking_records: self.show_info_dialog("入库记录", "没有入库记录") return records_text = "入库记录:\n\n" for record in self.container_yard.stacking_records: if record['operation'] == 'stack': c = record['container'] position = c['position'] pos_str = f"{position[0]},{position[1]},{position[2]}" if position else "未分配" records_text += f"集装箱 {c['id']} - {c['type']} (来自泊位{c['berth']})\n" records_text += f"位置: {pos_str}, 时间: {record['time']}\n" records_text += f"尺寸: {c['size']}, 重量: {c['weight']}吨, 所有者: {c['owner']}\n" records_text += f"目的地: {c['destination']}, 储存时间: {c['duration']}天\n\n" else: # collapse records_text += f"塌陷操作 - 位置: {record['position']}\n" records_text += f"受影响的集装箱: {', '.join(record['collapsed_containers'])}\n" records_text += f"时间: {record['time']}\n\n" self.show_info_dialog("入库记录", records_text) def show_collapse_records(self): """显示塌陷记录""" if not self.container_yard.collapse_records: self.show_info_dialog("塌陷记录", "没有塌陷记录") return records_text = "塌陷记录:\n\n" for record in self.container_yard.collapse_records: original_pos = f"{record['original_position'][0]},{record['original_position'][1]},{record['original_position'][2]}" records_text += f"集装箱 {record['container_id']}\n" records_text += f"原位置: {original_pos}, 时间: {record['time']}\n\n" self.show_info_dialog("塌陷记录", records_text) def show_exception_records(self): """显示异常记录""" if not self.container_yard.exceptions and not [r for r in self.container_yard.stacking_records if r['operation'] == 'exception']: self.show_info_dialog("异常记录", "没有异常记录") return records_text = "异常记录:\n\n" # 处理当前异常 for exception in self.container_yard.exceptions: records_text += f"时间: {exception['time']}\n" records_text += f"类型: {exception['type']}\n" records_text += f"内容: {exception['message']}\n" if 'container' in exception: c = exception['container'] records_text += f"相关集装箱: {c['id']} (类型: {c['type']}, 泊位: {c['berth']})\n" records_text += "\n" self.show_info_dialog("异常记录", records_text) def show_memo_dialog(self): """显示备忘录对话框""" dialog = tk.Toplevel(self.root) dialog.title("备忘录") dialog.geometry("500x400") ttk.Label(dialog, text="异常情况记录:", font=("Arial", 10, "bold")).pack(pady=5, anchor=tk.W, padx=10) # 异常记录文本框 memo_frame = ttk.Frame(dialog) memo_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) memo_text = scrolledtext.ScrolledText(memo_frame, wrap=tk.WORD) memo_text.pack(fill=tk.BOTH, expand=True) # 添加现有异常记录 for exception in self.container_yard.exceptions: memo_text.insert(tk.END, f"[{exception['time']}] {exception['message']}\n") # 添加新异常按钮 def add_exception(): exception_type = exception_var.get() description = description_entry.get("1.0", tk.END).strip() if not description: messagebox.showwarning("输入错误", "请输入异常描述") return new_exception = { 'type': exception_type, 'message': description, 'time': datetime.now().strftime("%Y-%m-%d %H:%M:%S") } self.container_yard.exceptions.append(new_exception) memo_text.insert(tk.END, f"[{new_exception['time']}] {description}\n") self.log_message(f"已记录异常: {exception_type} - {description[:30]}...") description_entry.delete("1.0", tk.END) exception_frame = ttk.Frame(dialog) exception_frame.pack(fill=tk.X, padx=10, pady=5) ttk.Label(exception_frame, text="异常类型:").pack(side=tk.LEFT, padx=5) exception_var = tk.StringVar() exception_combo = ttk.Combobox(exception_frame, textvariable=exception_var, width=12, values=["爆仓", "设备故障", "操作失误", "货物异常", "其他"]) exception_combo.pack(side=tk.LEFT, padx=5) exception_combo.current(0) ttk.Label(exception_frame, text="描述:").pack(side=tk.LEFT, padx=5) description_entry = tk.Text(exception_frame, height=3, width=30) description_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5) ttk.Button(exception_frame, text="添加记录", command=add_exception).pack(side=tk.RIGHT, padx=5) ttk.Button(dialog, text="关闭", command=dialog.destroy).pack(pady=10) def show_capacity_table(self): """显示堆场容量表""" capacity_data = self.container_yard.get_yard_capacity() dialog = tk.Toplevel(self.root) dialog.title("堆场容量表") dialog.geometry("800x600") # 创建表格 tree_frame = ttk.Frame(dialog) tree_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) tree = ttk.Treeview(tree_frame, columns=('row', 'col', 'utilization', 'types'), show='headings') # 设置列 tree.heading('row', text='行') tree.heading('col', text='列') tree.heading('utilization', text='利用率') tree.heading('types', text='货物类型分布') tree.column('row', width=50, anchor='center') tree.column('col', width=50, anchor='center') tree.column('utilization', width=100, anchor='center') tree.column('types', width=500, anchor='w') # 添加数据 for item in capacity_data: types_str = ", ".join(item['types']) tree.insert('', 'end', values=( item['row'], item['col'], item['utilization'], types_str )) # 添加滚动条 scrollbar = ttk.Scrollbar(tree_frame, orient=tk.VERTICAL, command=tree.yview) tree.configure(yscroll=scrollbar.set) # 布局 tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) # 统计信息 total_slots = self.container_yard.rows * self.container_yard.cols * self.container_yard.max_height used_slots = sum(len(item['types']) for item in capacity_data) utilization = used_slots / total_slots * 100 stats_frame = ttk.Frame(dialog) stats_frame.pack(fill=tk.X, padx=10, pady=5) ttk.Label(stats_frame, text=f"总容量: {total_slots} | 已使用: {used_slots} | 利用率: {utilization:.1f}%", font=("Arial", 10, "bold")).pack(pady=5) ttk.Button(dialog, text="关闭", command=dialog.destroy).pack(pady=10) def show_info_dialog(self, title, content): """显示信息对话框""" dialog = tk.Toplevel(self.root) dialog.title(title) dialog.geometry("600x400") text = scrolledtext.ScrolledText(dialog, wrap=tk.WORD) text.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) text.insert(tk.END, content) text.config(state=tk.DISABLED) btn = ttk.Button(dialog, text="关闭", command=dialog.destroy) btn.pack(pady=10) def show_figure_dialog(self, title, figure): """显示图表对话框""" dialog = tk.Toplevel(self.root) dialog.title(title) dialog.geometry("800x600") canvas = FigureCanvasTkAgg(figure, dialog) canvas.draw() canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True, padx=10, pady=10) btn = ttk.Button(dialog, text="关闭", command=dialog.destroy) btn.pack(pady=10) def save_records(self): """保存记录到文件""" try: filetypes = [("JSON文件", "*.json"), ("所有文件", "*.*")] filename = filedialog.asksaveasfilename( title="保存记录", filetypes=filetypes, defaultextension=".json") if filename: data = { 'container_count': self.container_count, 'stacking_records': self.container_yard.stacking_records, 'collapse_records': self.container_yard.collapse_records, 'exceptions': self.container_yard.exceptions, 'containers_to_add': self.containers_to_add, 'type_zones': self.container_yard.type_zones, 'berths': self.container_yard.berths } with open(filename, 'w') as f: json.dump(data, f, indent=2) self.log_message(f"记录已保存到 {filename}") except Exception as e: messagebox.showerror("保存错误", f"保存记录失败: {str(e)}") self.log_message(f"保存记录失败: {str(e)}") def load_records(self): """从文件加载记录""" try: filetypes = [("JSON文件", "*.json"), ("所有文件", "*.*")] filename = filedialog.askopenfilename( title="加载记录", filetypes=filetypes) if filename: with open(filename, 'r') as f: data = json.load(f) # 恢复数据 self.container_count = data.get('container_count', 0) self.container_yard.stacking_records = data.get('stacking_records', []) self.container_yard.collapse_records = data.get('collapse_records', []) self.container_yard.exceptions = data.get('exceptions', []) self.containers_to_add = data.get('containers_to_add', []) self.container_yard.type_zones = data.get('type_zones', self.container_yard.type_zones) self.container_yard.berths = data.get('berths', self.container_yard.berths) # 重建堆场 self.container_yard = ContainerYard(rows=15, cols=20, max_height=5) self.container_yard.stacking_records = data.get('stacking_records', []) self.container_yard.collapse_records = data.get('collapse_records', []) self.container_yard.exceptions = data.get('exceptions', []) self.container_yard.type_zones = data.get('type_zones', self.container_yard.type_zones) self.container_yard.berths = data.get('berths', self.container_yard.berths) # 重建堆场状态 for record in self.container_yard.stacking_records: if record['operation'] == 'stack': container = record['container'] if container['position']: row, col, height = container['position'] self.container_yard.yard[row, col, height] = container # 更新UI self.container_count_label.config(text=f"集装箱总数: {self.container_count}") # 更新小地图 ax = self.mini_map_figure.add_subplot(111) self.update_mini_map(ax) # 更新主地图 self.draw_map() self.log_message(f"记录已从 {filename} 加载") except Exception as e: messagebox.showerror("加载错误", f"加载记录失败: {str(e)}") self.log_message(f"加载记录失败: {str(e)}") if __name__ == "__main__": root = tk.Tk() app = ContainerInOutSystem(root) root.mainloop()
07-29
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值