{"id":63257,"date":"2026-01-21T18:16:56","date_gmt":"2026-01-21T10:16:56","guid":{"rendered":"https:\/\/www.wsisp.com\/helps\/63257.html"},"modified":"2026-01-21T18:16:56","modified_gmt":"2026-01-21T10:16:56","slug":"pyvista%e6%88%98%e5%9c%ba%e5%8f%af%e8%a7%86%e5%8c%96%e5%ae%9e%e6%88%98%ef%bc%88%e4%b8%89%ef%bc%89%ef%bc%9a%e9%9b%b7%e8%be%be%e4%b8%8e%e7%9b%ae%e6%a0%87%e8%bd%a8%e8%bf%b9%e5%8f%af%e8%a7%86%e5%8c%96","status":"publish","type":"post","link":"https:\/\/www.wsisp.com\/helps\/63257.html","title":{"rendered":"PyVista\u6218\u573a\u53ef\u89c6\u5316\u5b9e\u6218\uff08\u4e09\uff09\uff1a\u96f7\u8fbe\u4e0e\u76ee\u6807\u8f68\u8ff9\u53ef\u89c6\u5316"},"content":{"rendered":"<h3>\u6458\u8981<\/h3>\n<p>\u5728\u519b\u4e8b\u4eff\u771f\u3001\u7a7a\u4e2d\u4ea4\u901a\u7ba1\u5236\u3001\u65e0\u4eba\u673a\u76d1\u63a7\u7b49\u9886\u57df&#xff0c;\u8f68\u8ff9\u6570\u636e\u7684\u53ef\u89c6\u5316\u662f\u7406\u89e3\u76ee\u6807\u884c\u4e3a\u3001\u5206\u6790\u8fd0\u52a8\u89c4\u5f8b\u3001\u8bc4\u4f30\u6218\u672f\u6548\u679c\u7684\u5173\u952e\u3002\u672c\u6587\u662f&#034;PyVista\u96f7\u8fbe\u7535\u5b50\u5bf9\u6297\u6218\u573a\u6001\u52bf\u4eff\u771f&#034;\u7cfb\u5217\u7684\u7b2c\u4e09\u7bc7&#xff0c;\u4e13\u6ce8\u4e8e\u96f7\u8fbe\u4e0e\u76ee\u6807\u8f68\u8ff9\u76843D\u53ef\u89c6\u5316\u6280\u672f\u3002\u6211\u4eec\u5c06\u6df1\u5165\u63a2\u8ba8\u5982\u4f55\u4ece\u5404\u79cd\u6570\u636e\u6e90\u52a0\u8f7d\u8f68\u8ff9\u6570\u636e&#xff0c;\u57283D\u7a7a\u95f4\u4e2d\u521b\u5efa\u76f4\u89c2\u7684\u52a8\u6001\u8f68\u8ff9\u5c55\u793a&#xff0c;\u4ee5\u53ca\u5982\u4f55\u901a\u8fc7\u989c\u8272\u6620\u5c04\u3001\u65f6\u95f4\u6807\u7b7e\u3001\u591a\u7ef4\u5ea6\u4fe1\u606f\u53e0\u52a0\u7b49\u65b9\u5f0f\u589e\u5f3a\u8f68\u8ff9\u7684\u53ef\u8bfb\u6027\u548c\u4fe1\u606f\u5bc6\u5ea6\u3002\u901a\u8fc7\u4e09\u4e2a\u5b8c\u6574\u7684\u5b9e\u6218\u6848\u4f8b&#xff0c;\u8bfb\u8005\u5c06\u638c\u63e1\u8f68\u8ff9\u53ef\u89c6\u5316\u7684\u6838\u5fc3\u6280\u672f&#xff0c;\u4e3a\u6218\u573a\u6001\u52bf\u5206\u6790\u3001\u76ee\u6807\u884c\u4e3a\u5206\u6790\u7b49\u5e94\u7528\u6253\u4e0b\u575a\u5b9e\u57fa\u7840\u3002<\/p>\n<h3>1. \u5f15\u8a00&#xff1a;\u4e3a\u4ec0\u4e48\u9700\u8981\u8f68\u8ff9\u53ef\u89c6\u5316&#xff1f;<\/h3>\n<h4>1.1 \u8f68\u8ff9\u6570\u636e\u7684\u91cd\u8981\u6027<\/h4>\n<p>\u5728\u519b\u4e8b\u548c\u6c11\u7528\u9886\u57df&#xff0c;\u8f68\u8ff9\u6570\u636e\u627f\u8f7d\u7740\u4e30\u5bcc\u7684\u4fe1\u606f&#xff1a;<\/p>\n<ul>\n<li>\n<p>\u7a7a\u95f4\u4fe1\u606f&#xff1a;\u76ee\u6807\u7684\u5b9e\u65f6\u4f4d\u7f6e\u3001\u8fd0\u52a8\u8def\u5f84<\/p>\n<\/li>\n<li>\n<p>\u65f6\u95f4\u4fe1\u606f&#xff1a;\u76ee\u6807\u5728\u4e0d\u540c\u65f6\u95f4\u70b9\u7684\u72b6\u6001<\/p>\n<\/li>\n<li>\n<p>\u8fd0\u52a8\u5b66\u4fe1\u606f&#xff1a;\u901f\u5ea6\u3001\u52a0\u901f\u5ea6\u3001\u822a\u5411\u53d8\u5316<\/p>\n<\/li>\n<li>\n<p>\u73af\u5883\u4fe1\u606f&#xff1a;\u9ad8\u5ea6\u3001\u5730\u5f62\u5173\u8054\u3001\u6c14\u8c61\u5f71\u54cd<\/p>\n<\/li>\n<li>\n<p>\u6218\u672f\u4fe1\u606f&#xff1a;\u4f5c\u6218\u610f\u56fe\u3001\u5a01\u80c1\u7b49\u7ea7\u3001\u8fd0\u52a8\u6a21\u5f0f<\/p>\n<\/li>\n<\/ul>\n<h4>1.2 \u8f68\u8ff9\u53ef\u89c6\u5316\u7684\u6311\u6218<\/h4>\n<p>\u8f68\u8ff9\u53ef\u89c6\u5316\u9762\u4e34\u591a\u65b9\u9762\u7684\u6280\u672f\u6311\u6218&#xff1a;<\/p>\n<p style=\"text-align:center\"><img decoding=\"async\" alt=\"\" src=\"https:\/\/www.wsisp.com\/helps\/wp-content\/uploads\/2026\/01\/20260121101654-6970a79652ca0.png\" \/><\/p>\n<h4>1.3 PyVista\u5728\u8f68\u8ff9\u53ef\u89c6\u5316\u4e2d\u7684\u4f18\u52bf<\/h4>\n<p>PyVista\u63d0\u4f9b\u4e86\u5b8c\u6574\u76843D\u8f68\u8ff9\u53ef\u89c6\u5316\u89e3\u51b3\u65b9\u6848&#xff1a;<\/p>\n<li>\n<p>\u4e30\u5bcc\u7684\u51e0\u4f55\u4f53\u652f\u6301&#xff1a;\u652f\u6301\u70b9\u3001\u7ebf\u3001\u9762\u3001\u4f53\u7b49\u591a\u79cd\u51e0\u4f55\u8868\u793a<\/p>\n<\/li>\n<li>\n<p>\u7075\u6d3b\u7684\u989c\u8272\u6620\u5c04&#xff1a;\u652f\u6301\u8fde\u7eed\u3001\u79bb\u6563\u7684\u989c\u8272\u6620\u5c04\u65b9\u6848<\/p>\n<\/li>\n<li>\n<p>\u9ad8\u6548\u7684\u7ba1\u7ebf\u67b6\u6784&#xff1a;\u652f\u6301\u5927\u89c4\u6a21\u8f68\u8ff9\u6570\u636e\u7684\u5feb\u901f\u6e32\u67d3<\/p>\n<\/li>\n<li>\n<p>\u5b8c\u5584\u7684\u65f6\u95f4\u652f\u6301&#xff1a;\u652f\u6301\u65f6\u95f4\u5e8f\u5217\u6570\u636e\u7684\u52a8\u6001\u5c55\u793a<\/p>\n<\/li>\n<li>\n<p>\u4ea4\u4e92\u5f0f\u5206\u6790\u5de5\u5177&#xff1a;\u652f\u6301\u8f68\u8ff9\u7684\u9009\u53d6\u3001\u6d4b\u91cf\u3001\u5206\u6790<\/p>\n<\/li>\n<h3>2. \u6570\u636e\u51c6\u5907&#xff1a;\u8f68\u8ff9\u6570\u636e\u7684\u683c\u5f0f\u4e0e\u5904\u7406<\/h3>\n<h4>2.1 \u5e38\u89c1\u7684\u8f68\u8ff9\u6570\u636e\u683c\u5f0f<\/h4>\n<p>\u8f68\u8ff9\u6570\u636e\u53ef\u4ee5\u6709\u591a\u79cd\u683c\u5f0f&#xff0c;\u6211\u4eec\u9700\u8981\u6839\u636e\u6570\u636e\u6e90\u7684\u7279\u70b9\u9009\u62e9\u5408\u9002\u7684\u5904\u7406\u65b9\u6cd5&#xff1a;<\/p>\n<p># 1. CSV\u683c\u5f0f\u793a\u4f8b<br \/>\ncsv_data_example &#061; &#034;&#034;&#034;timestamp,object_id,latitude,longitude,altitude,speed,course<br \/>\n2024-01-01 12:00:00,001,39.9042,116.4074,10000,250,45<br \/>\n2024-01-01 12:00:10,001,39.9055,116.4101,10100,255,46<br \/>\n2024-01-01 12:00:20,001,39.9068,116.4128,10200,260,47&#034;&#034;&#034;<\/p>\n<p># 2. JSON\u683c\u5f0f\u793a\u4f8b<br \/>\njson_data_example &#061; {<br \/>\n    &#034;trajectory&#034;: {<br \/>\n        &#034;id&#034;: &#034;001&#034;,<br \/>\n        &#034;points&#034;: [<br \/>\n            {&#034;t&#034;: 0, &#034;x&#034;: 100, &#034;y&#034;: 200, &#034;z&#034;: 3000, &#034;v&#034;: 250},<br \/>\n            {&#034;t&#034;: 10, &#034;x&#034;: 150, &#034;y&#034;: 220, &#034;z&#034;: 3100, &#034;v&#034;: 255}<br \/>\n        ]<br \/>\n    }<br \/>\n}<\/p>\n<p># 3. \u4e8c\u8fdb\u5236\u683c\u5f0f&#xff08;\u9002\u5408\u5927\u89c4\u6a21\u6570\u636e&#xff09;<br \/>\nimport struct<br \/>\nbinary_format &#061; struct.Struct(&#039;3f2f&#039;)  # x,y,z,v,heading<\/p>\n<h4>2.2 \u8f68\u8ff9\u6570\u636e\u5904\u7406\u57fa\u7c7b<\/h4>\n<p>\u521b\u5efa\u4e00\u4e2a\u901a\u7528\u7684\u8f68\u8ff9\u6570\u636e\u5904\u7406\u57fa\u7c7b&#xff0c;\u652f\u6301\u591a\u79cd\u6570\u636e\u683c\u5f0f&#xff1a;<\/p>\n<p>import pandas as pd<br \/>\nimport numpy as np<br \/>\nimport json<br \/>\nimport csv<br \/>\nfrom datetime import datetime, timedelta<br \/>\nimport math<\/p>\n<p>class TrajectoryData:<br \/>\n    &#034;&#034;&#034;\u8f68\u8ff9\u6570\u636e\u5904\u7406\u57fa\u7c7b&#034;&#034;&#034;<\/p>\n<p>    def __init__(self):<br \/>\n        self.trajectories &#061; {}  # \u8f68\u8ff9\u5b57\u5178 {id: Trajectory}<br \/>\n        self.coordinate_system &#061; &#039;cartesian&#039;  # \u5750\u6807\u7cfb\u7c7b\u578b<br \/>\n        self.time_zone &#061; &#039;UTC&#039;  # \u65f6\u533a<\/p>\n<p>    class TrajectoryPoint:<br \/>\n        &#034;&#034;&#034;\u8f68\u8ff9\u70b9\u7c7b&#034;&#034;&#034;<\/p>\n<p>        def __init__(self, timestamp, position, **kwargs):<br \/>\n            self.timestamp &#061; timestamp<br \/>\n            self.position &#061; np.array(position, dtype&#061;float)  # [x, y, z]<\/p>\n<p>            # \u8fd0\u52a8\u5b66\u53c2\u6570<br \/>\n            self.velocity &#061; kwargs.get(&#039;velocity&#039;, 0.0)  # \u901f\u5ea6<br \/>\n            self.heading &#061; kwargs.get(&#039;heading&#039;, 0.0)  # \u822a\u5411<br \/>\n            self.pitch &#061; kwargs.get(&#039;pitch&#039;, 0.0)  # \u4fef\u4ef0<br \/>\n            self.acceleration &#061; kwargs.get(&#039;acceleration&#039;, 0.0)  # \u52a0\u901f\u5ea6<\/p>\n<p>            # \u5176\u4ed6\u53c2\u6570<br \/>\n            self.attributes &#061; kwargs  # \u6240\u6709\u5c5e\u6027<\/p>\n<p>        def __repr__(self):<br \/>\n            return f&#034;Point(t&#061;{self.timestamp}, pos&#061;{self.position}, v&#061;{self.velocity})&#034;<\/p>\n<p>    class Trajectory:<br \/>\n        &#034;&#034;&#034;\u8f68\u8ff9\u7c7b&#034;&#034;&#034;<\/p>\n<p>        def __init__(self, trajectory_id, object_type&#061;&#034;unknown&#034;, color&#061;None):<br \/>\n            self.id &#061; trajectory_id<br \/>\n            self.object_type &#061; object_type<br \/>\n            self.color &#061; color<br \/>\n            self.points &#061; []  # TrajectoryPoint\u5217\u8868<br \/>\n            self.start_time &#061; None<br \/>\n            self.end_time &#061; None<br \/>\n            self.duration &#061; 0.0<br \/>\n            self.total_distance &#061; 0.0<\/p>\n<p>        def add_point(self, point):<br \/>\n            &#034;&#034;&#034;\u6dfb\u52a0\u8f68\u8ff9\u70b9&#034;&#034;&#034;<br \/>\n            if not self.points:<br \/>\n                self.start_time &#061; point.timestamp<br \/>\n            self.points.append(point)<br \/>\n            self.end_time &#061; point.timestamp<\/p>\n<p>            # \u8ba1\u7b97\u7d2f\u79ef\u8ddd\u79bb<br \/>\n            if len(self.points) &gt; 1:<br \/>\n                last_point &#061; self.points[-2]<br \/>\n                distance &#061; np.linalg.norm(point.position &#8211; last_point.position)<br \/>\n                self.total_distance &#043;&#061; distance<\/p>\n<p>            # \u8ba1\u7b97\u6301\u7eed\u65f6\u95f4<br \/>\n            if self.start_time and self.end_time:<br \/>\n                if isinstance(self.start_time, (int, float)) and isinstance(self.end_time, (int, float)):<br \/>\n                    self.duration &#061; self.end_time &#8211; self.start_time<br \/>\n                else:<br \/>\n                    # \u5982\u679c\u662fdatetime\u5bf9\u8c61<br \/>\n                    self.duration &#061; (self.end_time &#8211; self.start_time).total_seconds()<\/p>\n<p>        def get_point_at_time(self, timestamp):<br \/>\n            &#034;&#034;&#034;\u83b7\u53d6\u6307\u5b9a\u65f6\u95f4\u7684\u8f68\u8ff9\u70b9&#xff08;\u63d2\u503c&#xff09;&#034;&#034;&#034;<br \/>\n            if not self.points:<br \/>\n                return None<\/p>\n<p>            # \u627e\u5230\u6700\u63a5\u8fd1\u7684\u4e24\u4e2a\u70b9<br \/>\n            for i in range(len(self.points) &#8211; 1):<br \/>\n                p1 &#061; self.points[i]<br \/>\n                p2 &#061; self.points[i &#043; 1]<\/p>\n<p>                if p1.timestamp &lt;&#061; timestamp &lt;&#061; p2.timestamp:<br \/>\n                    # \u7ebf\u6027\u63d2\u503c<br \/>\n                    ratio &#061; (timestamp &#8211; p1.timestamp) \/ (p2.timestamp &#8211; p1.timestamp)<\/p>\n<p>                    # \u4f4d\u7f6e\u63d2\u503c<br \/>\n                    position &#061; p1.position &#043; (p2.position &#8211; p1.position) * ratio<\/p>\n<p>                    # \u901f\u5ea6\u63d2\u503c<br \/>\n                    velocity &#061; p1.velocity &#043; (p2.velocity &#8211; p1.velocity) * ratio<\/p>\n<p>                    # \u521b\u5efa\u63d2\u503c\u70b9<br \/>\n                    interpolated_point &#061; self.__class__.TrajectoryPoint(<br \/>\n                        timestamp&#061;timestamp,<br \/>\n                        position&#061;position,<br \/>\n                        velocity&#061;velocity,<br \/>\n                        heading&#061;p1.heading &#043; (p2.heading &#8211; p1.heading) * ratio<br \/>\n                    )<\/p>\n<p>                    return interpolated_point<\/p>\n<p>            return None<\/p>\n<p>        def resample(self, interval&#061;1.0):<br \/>\n            &#034;&#034;&#034;\u91cd\u65b0\u91c7\u6837\u8f68\u8ff9\u70b9&#034;&#034;&#034;<br \/>\n            if not self.points or len(self.points) &lt; 2:<br \/>\n                return<\/p>\n<p>            resampled_points &#061; []<br \/>\n            current_time &#061; self.start_time<\/p>\n<p>            while current_time &lt;&#061; self.end_time:<br \/>\n                point &#061; self.get_point_at_time(current_time)<br \/>\n                if point:<br \/>\n                    resampled_points.append(point)<br \/>\n                current_time &#043;&#061; interval<\/p>\n<p>            self.points &#061; resampled_points<\/p>\n<p>        def get_statistics(self):<br \/>\n            &#034;&#034;&#034;\u83b7\u53d6\u8f68\u8ff9\u7edf\u8ba1\u4fe1\u606f&#034;&#034;&#034;<br \/>\n            if not self.points:<br \/>\n                return {}<\/p>\n<p>            speeds &#061; [p.velocity for p in self.points]<br \/>\n            altitudes &#061; [p.position[2] for p in self.points]<br \/>\n            headings &#061; [p.heading for p in self.points]<\/p>\n<p>            return {<br \/>\n                &#039;point_count&#039;: len(self.points),<br \/>\n                &#039;duration&#039;: self.duration,<br \/>\n                &#039;total_distance&#039;: self.total_distance,<br \/>\n                &#039;avg_speed&#039;: np.mean(speeds) if speeds else 0,<br \/>\n                &#039;max_speed&#039;: max(speeds) if speeds else 0,<br \/>\n                &#039;min_altitude&#039;: min(altitudes) if altitudes else 0,<br \/>\n                &#039;max_altitude&#039;: max(altitudes) if altitudes else 0,<br \/>\n                &#039;avg_heading&#039;: np.mean(headings) if headings else 0<br \/>\n            }<\/p>\n<p>    def load_from_csv(self, filepath, time_column&#061;&#039;timestamp&#039;, id_column&#061;&#039;object_id&#039;,<br \/>\n                     x_column&#061;&#039;x&#039;, y_column&#061;&#039;y&#039;, z_column&#061;&#039;z&#039;, **kwargs):<br \/>\n        &#034;&#034;&#034;\u4eceCSV\u6587\u4ef6\u52a0\u8f7d\u8f68\u8ff9\u6570\u636e&#034;&#034;&#034;<br \/>\n        print(f&#034;\u52a0\u8f7dCSV\u6587\u4ef6: {filepath}&#034;)<\/p>\n<p>        try:<br \/>\n            df &#061; pd.read_csv(filepath)<br \/>\n            print(f&#034;\u6570\u636e\u5f62\u72b6: {df.shape}&#034;)<br \/>\n            print(f&#034;\u5217\u540d: {list(df.columns)}&#034;)<\/p>\n<p>            # \u6309\u76ee\u6807ID\u5206\u7ec4<br \/>\n            if id_column in df.columns:<br \/>\n                grouped &#061; df.groupby(id_column)<\/p>\n<p>                for obj_id, group in grouped:<br \/>\n                    print(f&#034;\u5904\u7406\u76ee\u6807 {obj_id}&#xff0c;\u6570\u636e\u70b9: {len(group)}&#034;)<\/p>\n<p>                    # \u521b\u5efa\u8f68\u8ff9<br \/>\n                    trajectory &#061; self.Trajectory(str(obj_id))<\/p>\n<p>                    # \u5904\u7406\u6bcf\u4e2a\u6570\u636e\u70b9<br \/>\n                    for _, row in group.iterrows():<br \/>\n                        # \u89e3\u6790\u65f6\u95f4<br \/>\n                        timestamp &#061; row[time_column]<br \/>\n                        if isinstance(timestamp, str):<br \/>\n                            try:<br \/>\n                                timestamp &#061; pd.to_datetime(timestamp)<br \/>\n                            except:<br \/>\n                                # \u8f6c\u6362\u4e3a\u65f6\u95f4\u6233<br \/>\n                                timestamp &#061; float(timestamp)<\/p>\n<p>                        # \u89e3\u6790\u4f4d\u7f6e<br \/>\n                        x &#061; float(row[x_column]) if x_column in row else 0.0<br \/>\n                        y &#061; float(row[y_column]) if y_column in row else 0.0<br \/>\n                        z &#061; float(row[z_column]) if z_column in row else 0.0<\/p>\n<p>                        # \u83b7\u53d6\u5176\u4ed6\u5c5e\u6027<br \/>\n                        attributes &#061; {}<br \/>\n                        for col, val in row.items():<br \/>\n                            if col not in [time_column, id_column, x_column, y_column, z_column]:<br \/>\n                                try:<br \/>\n                                    attributes[col] &#061; float(val)<br \/>\n                                except:<br \/>\n                                    attributes[col] &#061; val<\/p>\n<p>                        # \u521b\u5efa\u8f68\u8ff9\u70b9<br \/>\n                        point &#061; self.TrajectoryPoint(<br \/>\n                            timestamp&#061;timestamp,<br \/>\n                            position&#061;[x, y, z],<br \/>\n                            **attributes<br \/>\n                        )<\/p>\n<p>                        trajectory.add_point(point)<\/p>\n<p>                    # \u6dfb\u52a0\u5230\u8f68\u8ff9\u5b57\u5178<br \/>\n                    self.trajectories[trajectory.id] &#061; trajectory<\/p>\n<p>            else:<br \/>\n                print(f&#034;\u8b66\u544a: \u672a\u627e\u5230ID\u5217 &#039;{id_column}&#039;&#034;)<\/p>\n<p>        except Exception as e:<br \/>\n            print(f&#034;\u52a0\u8f7dCSV\u6587\u4ef6\u5931\u8d25: {e}&#034;)<br \/>\n            import traceback<br \/>\n            traceback.print_exc()<\/p>\n<p>    def load_from_json(self, filepath):<br \/>\n        &#034;&#034;&#034;\u4eceJSON\u6587\u4ef6\u52a0\u8f7d\u8f68\u8ff9\u6570\u636e&#034;&#034;&#034;<br \/>\n        with open(filepath, &#039;r&#039;, encoding&#061;&#039;utf-8&#039;) as f:<br \/>\n            data &#061; json.load(f)<\/p>\n<p>        if isinstance(data, list):<br \/>\n            for item in data:<br \/>\n                self._load_single_trajectory_from_dict(item)<br \/>\n        elif isinstance(data, dict):<br \/>\n            if &#039;trajectories&#039; in data:<br \/>\n                for traj_data in data[&#039;trajectories&#039;]:<br \/>\n                    self._load_single_trajectory_from_dict(traj_data)<br \/>\n            else:<br \/>\n                self._load_single_trajectory_from_dict(data)<\/p>\n<p>    def _load_single_trajectory_from_dict(self, data):<br \/>\n        &#034;&#034;&#034;\u4ece\u5b57\u5178\u52a0\u8f7d\u5355\u4e2a\u8f68\u8ff9&#034;&#034;&#034;<br \/>\n        traj_id &#061; data.get(&#039;id&#039;, str(len(self.trajectories)))<br \/>\n        traj_type &#061; data.get(&#039;type&#039;, &#039;unknown&#039;)<\/p>\n<p>        trajectory &#061; self.Trajectory(traj_id, traj_type)<\/p>\n<p>        points &#061; data.get(&#039;points&#039;, [])<br \/>\n        for point_data in points:<br \/>\n            timestamp &#061; point_data.get(&#039;t&#039;, 0)<br \/>\n            x &#061; point_data.get(&#039;x&#039;, 0.0)<br \/>\n            y &#061; point_data.get(&#039;y&#039;, 0.0)<br \/>\n            z &#061; point_data.get(&#039;z&#039;, 0.0)<\/p>\n<p>            point &#061; self.TrajectoryPoint(<br \/>\n                timestamp&#061;timestamp,<br \/>\n                position&#061;[x, y, z],<br \/>\n                velocity&#061;point_data.get(&#039;v&#039;, 0.0),<br \/>\n                heading&#061;point_data.get(&#039;heading&#039;, 0.0)<br \/>\n            )<\/p>\n<p>            trajectory.add_point(point)<\/p>\n<p>        self.trajectories[trajectory.id] &#061; trajectory<\/p>\n<p>    def convert_coordinates(self, from_system&#061;&#039;wgs84&#039;, to_system&#061;&#039;cartesian&#039;):<br \/>\n        &#034;&#034;&#034;\u5750\u6807\u8f6c\u6362&#xff08;\u7b80\u5316\u7248\u672c&#xff09;&#034;&#034;&#034;<br \/>\n        if from_system &#061;&#061; &#039;wgs84&#039; and to_system &#061;&#061; &#039;cartesian&#039;:<br \/>\n            # \u7b80\u5316\u7684WGS84\u5230\u7b1b\u5361\u5c14\u5750\u6807\u8f6c\u6362<br \/>\n            for traj_id, trajectory in self.trajectories.items():<br \/>\n                for point in trajectory.points:<br \/>\n                    # \u83b7\u53d6\u7ecf\u7eac\u9ad8<br \/>\n                    lon, lat, alt &#061; point.position<\/p>\n<p>                    # \u8f6c\u6362\u4e3a\u5f27\u5ea6<br \/>\n                    lat_rad &#061; math.radians(lat)<br \/>\n                    lon_rad &#061; math.radians(lon)<\/p>\n<p>                    # \u7b80\u5316\u7684\u8f6c\u6362&#xff08;\u9002\u5408\u5c0f\u8303\u56f4\u533a\u57df&#xff09;<br \/>\n                    R &#061; 6371000  # \u5730\u7403\u534a\u5f84&#xff08;\u7c73&#xff09;<br \/>\n                    x &#061; (R &#043; alt) * math.cos(lat_rad) * math.cos(lon_rad)<br \/>\n                    y &#061; (R &#043; alt) * math.cos(lat_rad) * math.sin(lon_rad)<br \/>\n                    z &#061; (R &#043; alt) * math.sin(lat_rad)<\/p>\n<p>                    point.position &#061; np.array([x, y, z])<\/p>\n<p>    def get_trajectory(self, trajectory_id):<br \/>\n        &#034;&#034;&#034;\u83b7\u53d6\u6307\u5b9aID\u7684\u8f68\u8ff9&#034;&#034;&#034;<br \/>\n        return self.trajectories.get(trajectory_id)<\/p>\n<p>    def get_all_trajectories(self):<br \/>\n        &#034;&#034;&#034;\u83b7\u53d6\u6240\u6709\u8f68\u8ff9&#034;&#034;&#034;<br \/>\n        return list(self.trajectories.values())<\/p>\n<p>    def get_summary(self):<br \/>\n        &#034;&#034;&#034;\u83b7\u53d6\u6570\u636e\u6458\u8981&#034;&#034;&#034;<br \/>\n        return {<br \/>\n            &#039;trajectory_count&#039;: len(self.trajectories),<br \/>\n            &#039;total_points&#039;: sum(len(t.points) for t in self.trajectories.values()),<br \/>\n            &#039;trajectory_ids&#039;: list(self.trajectories.keys())<br \/>\n        }<\/p>\n<h4>2.3 \u8f68\u8ff9\u6570\u636e\u91c7\u6837\u4e0e\u4f18\u5316<\/h4>\n<p>\u5bf9\u4e8e\u5927\u89c4\u6a21\u8f68\u8ff9\u6570\u636e&#xff0c;\u9700\u8981\u8fdb\u884c\u91c7\u6837\u548c\u4f18\u5316\u4ee5\u63d0\u9ad8\u53ef\u89c6\u5316\u6027\u80fd&#xff1a;<\/p>\n<p>class TrajectoryOptimizer:<br \/>\n    &#034;&#034;&#034;\u8f68\u8ff9\u4f18\u5316\u5668&#034;&#034;&#034;<\/p>\n<p>    &#064;staticmethod<br \/>\n    def douglas_peucker(points, epsilon):<br \/>\n        &#034;&#034;&#034;\u9053\u683c\u62c9\u65af-\u666e\u514b\u7b97\u6cd5\u7b80\u5316\u8f68\u8ff9&#034;&#034;&#034;<br \/>\n        if len(points) &lt; 3:<br \/>\n            return points<\/p>\n<p>        # \u627e\u5230\u8ddd\u79bb\u6700\u8fdc\u7684\u70b9<br \/>\n        dmax &#061; 0<br \/>\n        index &#061; 0<\/p>\n<p>        for i in range(1, len(points) &#8211; 1):<br \/>\n            d &#061; TrajectoryOptimizer._perpendicular_distance(<br \/>\n                points[i], points[0], points[-1]<br \/>\n            )<br \/>\n            if d &gt; dmax:<br \/>\n                dmax &#061; d<br \/>\n                index &#061; i<\/p>\n<p>        # \u9012\u5f52\u7b80\u5316<br \/>\n        if dmax &gt; epsilon:<br \/>\n            left &#061; TrajectoryOptimizer.douglas_peucker(points[:index&#043;1], epsilon)<br \/>\n            right &#061; TrajectoryOptimizer.douglas_peucker(points[index:], epsilon)<br \/>\n            return left[:-1] &#043; right<br \/>\n        else:<br \/>\n            return [points[0], points[-1]]<\/p>\n<p>    &#064;staticmethod<br \/>\n    def _perpendicular_distance(point, line_start, line_end):<br \/>\n        &#034;&#034;&#034;\u8ba1\u7b97\u70b9\u5230\u76f4\u7ebf\u7684\u5782\u76f4\u8ddd\u79bb&#034;&#034;&#034;<br \/>\n        if np.array_equal(line_start, line_end):<br \/>\n            return np.linalg.norm(point &#8211; line_start)<\/p>\n<p>        # \u8ba1\u7b97\u70b9\u5230\u76f4\u7ebf\u7684\u8ddd\u79bb<br \/>\n        n &#061; np.linalg.norm(line_end &#8211; line_start)<br \/>\n        distance &#061; np.linalg.norm(<br \/>\n            np.cross(line_end &#8211; line_start, line_start &#8211; point)<br \/>\n        ) \/ n<\/p>\n<p>        return distance<\/p>\n<p>    &#064;staticmethod<br \/>\n    def uniform_sampling(points, target_count):<br \/>\n        &#034;&#034;&#034;\u5747\u5300\u91c7\u6837&#034;&#034;&#034;<br \/>\n        if len(points) &lt;&#061; target_count:<br \/>\n            return points<\/p>\n<p>        indices &#061; np.linspace(0, len(points) &#8211; 1, target_count, dtype&#061;int)<br \/>\n        return [points[i] for i in indices]<\/p>\n<p>    &#064;staticmethod<br \/>\n    def temporal_sampling(trajectory, time_interval):<br \/>\n        &#034;&#034;&#034;\u65f6\u95f4\u5747\u5300\u91c7\u6837&#034;&#034;&#034;<br \/>\n        if not trajectory.points:<br \/>\n            return trajectory<\/p>\n<p>        resampled &#061; TrajectoryData.Trajectory(trajectory.id, trajectory.object_type)<\/p>\n<p>        current_time &#061; trajectory.start_time<br \/>\n        while current_time &lt;&#061; trajectory.end_time:<br \/>\n            point &#061; trajectory.get_point_at_time(current_time)<br \/>\n            if point:<br \/>\n                resampled.add_point(point)<br \/>\n            current_time &#043;&#061; time_interval<\/p>\n<p>        return resampled<\/p>\n<p>    &#064;staticmethod<br \/>\n    def speed_based_sampling(trajectory, speed_threshold&#061;0.1):<br \/>\n        &#034;&#034;&#034;\u57fa\u4e8e\u901f\u5ea6\u53d8\u5316\u7684\u91c7\u6837&#034;&#034;&#034;<br \/>\n        if len(trajectory.points) &lt; 2:<br \/>\n            return trajectory<\/p>\n<p>        simplified_points &#061; [trajectory.points[0]]<\/p>\n<p>        for i in range(1, len(trajectory.points) &#8211; 1):<br \/>\n            # \u8ba1\u7b97\u901f\u5ea6\u53d8\u5316<br \/>\n            speed_change &#061; abs(<br \/>\n                trajectory.points[i].velocity &#8211; trajectory.points[i-1].velocity<br \/>\n            )<\/p>\n<p>            # \u5982\u679c\u901f\u5ea6\u53d8\u5316\u663e\u8457&#xff0c;\u4fdd\u7559\u8be5\u70b9<br \/>\n            if speed_change &gt; speed_threshold:<br \/>\n                simplified_points.append(trajectory.points[i])<\/p>\n<p>        simplified_points.append(trajectory.points[-1])<\/p>\n<p>        simplified_traj &#061; TrajectoryData.Trajectory(trajectory.id, trajectory.object_type)<br \/>\n        for point in simplified_points:<br \/>\n            simplified_traj.add_point(point)<\/p>\n<p>        return simplified_traj<\/p>\n<h3>3. \u57fa\u7840\u8f68\u8ff9\u53ef\u89c6\u5316\u6280\u672f<\/h3>\n<h4>3.1 \u7b80\u5355\u8f68\u8ff9\u7ebf\u7ed8\u5236<\/h4>\n<p>\u6700\u57fa\u672c\u7684\u8f68\u8ff9\u53ef\u89c6\u5316\u662f\u7ed8\u5236\u8fde\u63a5\u8f68\u8ff9\u70b9\u7684\u7ebf&#xff1a;<\/p>\n<p>import pyvista as pv<br \/>\nimport numpy as np<\/p>\n<p>class BasicTrajectoryVisualizer:<br \/>\n    &#034;&#034;&#034;\u57fa\u7840\u8f68\u8ff9\u53ef\u89c6\u5316\u5668&#034;&#034;&#034;<\/p>\n<p>    def __init__(self):<br \/>\n        self.plotter &#061; None<br \/>\n        self.trajectory_meshes &#061; {}<\/p>\n<p>    def create_simple_line(self, points, color&#061;&#039;white&#039;, line_width&#061;2, name&#061;None):<br \/>\n        &#034;&#034;&#034;\u521b\u5efa\u7b80\u5355\u8f68\u8ff9\u7ebf&#034;&#034;&#034;<br \/>\n        if len(points) &lt; 2:<br \/>\n            return None<\/p>\n<p>        # \u8f6c\u6362\u4e3aNumPy\u6570\u7ec4<br \/>\n        points_array &#061; np.array(points)<\/p>\n<p>        # \u521b\u5efa\u7ebf<br \/>\n        line &#061; pv.lines_from_points(points_array)<\/p>\n<p>        # \u6dfb\u52a0\u5c5e\u6027<br \/>\n        line[&#039;distance&#039;] &#061; np.linspace(0, 1, len(points_array))<\/p>\n<p>        return line<\/p>\n<p>    def create_spline(self, points, resolution&#061;100, degree&#061;3, name&#061;None):<br \/>\n        &#034;&#034;&#034;\u521b\u5efa\u6837\u6761\u66f2\u7ebf&#034;&#034;&#034;<br \/>\n        if len(points) &lt; 2:<br \/>\n            return None<\/p>\n<p>        # \u8f6c\u6362\u4e3aNumPy\u6570\u7ec4<br \/>\n        points_array &#061; np.array(points)<\/p>\n<p>        # \u521b\u5efa\u6837\u6761\u66f2\u7ebf<br \/>\n        spline &#061; pv.Spline(points_array, resolution)<\/p>\n<p>        return spline<\/p>\n<p>    def create_tube(self, points, radius&#061;0.5, n_sides&#061;8, name&#061;None):<br \/>\n        &#034;&#034;&#034;\u521b\u5efa\u7ba1\u9053\u8f68\u8ff9&#034;&#034;&#034;<br \/>\n        if len(points) &lt; 2:<br \/>\n            return None<\/p>\n<p>        # \u521b\u5efa\u6837\u6761\u66f2\u7ebf<br \/>\n        spline &#061; self.create_spline(points)<br \/>\n        if spline is None:<br \/>\n            return None<\/p>\n<p>        # \u521b\u5efa\u7ba1\u9053<br \/>\n        tube &#061; spline.tube(radius&#061;radius, n_sides&#061;n_sides)<\/p>\n<p>        return tube<\/p>\n<p>    def add_trajectory_line(self, trajectory, style&#061;&#039;line&#039;, **kwargs):<br \/>\n        &#034;&#034;&#034;\u6dfb\u52a0\u8f68\u8ff9\u7ebf\u5230\u573a\u666f&#034;&#034;&#034;<br \/>\n        if not trajectory.points:<br \/>\n            return None<\/p>\n<p>        # \u63d0\u53d6\u4f4d\u7f6e\u70b9<br \/>\n        points &#061; [p.position for p in trajectory.points]<\/p>\n<p>        # \u6839\u636e\u6837\u5f0f\u521b\u5efa\u8f68\u8ff9<br \/>\n        if style &#061;&#061; &#039;line&#039;:<br \/>\n            mesh &#061; self.create_simple_line(points, **kwargs)<br \/>\n        elif style &#061;&#061; &#039;spline&#039;:<br \/>\n            mesh &#061; self.create_spline(points, **kwargs)<br \/>\n        elif style &#061;&#061; &#039;tube&#039;:<br \/>\n            mesh &#061; self.create_tube(points, **kwargs)<br \/>\n        else:<br \/>\n            mesh &#061; self.create_simple_line(points, **kwargs)<\/p>\n<p>        if mesh is None:<br \/>\n            return None<\/p>\n<p>        # \u6dfb\u52a0\u5230\u8f68\u8ff9\u7f51\u683c\u5b57\u5178<br \/>\n        traj_id &#061; trajectory.id<br \/>\n        self.trajectory_meshes[traj_id] &#061; mesh<\/p>\n<p>        return mesh<\/p>\n<p>    def visualize_trajectories(self, trajectories, window_size&#061;(1200, 800),<br \/>\n                              show_axes&#061;True, show_grid&#061;True):<br \/>\n        &#034;&#034;&#034;\u53ef\u89c6\u5316\u591a\u6761\u8f68\u8ff9&#034;&#034;&#034;<br \/>\n        # \u521b\u5efa\u7ed8\u56fe\u7a97\u53e3<br \/>\n        self.plotter &#061; pv.Plotter(window_size&#061;window_size, title&#061;&#034;\u8f68\u8ff9\u53ef\u89c6\u5316&#034;)<\/p>\n<p>        # \u6dfb\u52a0\u8f68\u8ff9<br \/>\n        for trajectory in trajectories:<br \/>\n            # \u968f\u673a\u989c\u8272<br \/>\n            color &#061; np.random.rand(3)<\/p>\n<p>            # \u521b\u5efa\u8f68\u8ff9\u7ebf<br \/>\n            mesh &#061; self.add_trajectory_line(<br \/>\n                trajectory,<br \/>\n                style&#061;&#039;tube&#039;,<br \/>\n                radius&#061;0.3,<br \/>\n                color&#061;color<br \/>\n            )<\/p>\n<p>            if mesh and self.plotter:<br \/>\n                # \u6dfb\u52a0\u8f68\u8ff9\u7ebf<br \/>\n                self.plotter.add_mesh(<br \/>\n                    mesh,<br \/>\n                    color&#061;color,<br \/>\n                    opacity&#061;0.8,<br \/>\n                    name&#061;f&#039;trajectory_{trajectory.id}&#039;,<br \/>\n                    show_edges&#061;False<br \/>\n                )<\/p>\n<p>                # \u6dfb\u52a0\u8f68\u8ff9\u70b9<br \/>\n                points &#061; np.array([p.position for p in trajectory.points])<br \/>\n                if len(points) &gt; 0:<br \/>\n                    # \u5747\u5300\u91c7\u6837\u70b9<br \/>\n                    sample_indices &#061; np.linspace(0, len(points)-1, 20, dtype&#061;int)<br \/>\n                    sample_points &#061; points[sample_indices]<\/p>\n<p>                    points_mesh &#061; pv.PolyData(sample_points)<br \/>\n                    self.plotter.add_mesh(<br \/>\n                        points_mesh,<br \/>\n                        color&#061;color,<br \/>\n                        point_size&#061;5,<br \/>\n                        render_points_as_spheres&#061;True,<br \/>\n                        name&#061;f&#039;points_{trajectory.id}&#039;<br \/>\n                    )<\/p>\n<p>        # \u8bbe\u7f6e\u573a\u666f<br \/>\n        if show_axes:<br \/>\n            self.plotter.add_axes()<\/p>\n<p>        if show_grid:<br \/>\n            self.plotter.show_grid()<\/p>\n<p>        # \u8bbe\u7f6e\u76f8\u673a<br \/>\n        self.plotter.camera_position &#061; [(100, 100, 100), (0, 0, 0), (0, 0, 1)]<br \/>\n        self.plotter.set_background(&#039;black&#039;)<\/p>\n<p>        # \u663e\u793a<br \/>\n        self.plotter.show()<\/p>\n<h4>3.2 \u989c\u8272\u6620\u5c04\u4e0e\u8f68\u8ff9\u5c5e\u6027\u53ef\u89c6\u5316<\/h4>\n<p>\u8f68\u8ff9\u7684\u4e0d\u540c\u5c5e\u6027\u53ef\u4ee5\u901a\u8fc7\u989c\u8272\u6620\u5c04\u6765\u53ef\u89c6\u5316&#xff1a;<\/p>\n<p>class EnhancedTrajectoryVisualizer(BasicTrajectoryVisualizer):<br \/>\n    &#034;&#034;&#034;\u589e\u5f3a\u8f68\u8ff9\u53ef\u89c6\u5316\u5668&#xff08;\u652f\u6301\u989c\u8272\u6620\u5c04&#xff09;&#034;&#034;&#034;<\/p>\n<p>    def create_colored_trajectory(self, trajectory, color_by&#061;&#039;speed&#039;,<br \/>\n                                 colormap&#061;&#039;plasma&#039;, style&#061;&#039;tube&#039;, **kwargs):<br \/>\n        &#034;&#034;&#034;\u521b\u5efa\u5e26\u989c\u8272\u6620\u5c04\u7684\u8f68\u8ff9&#034;&#034;&#034;<br \/>\n        if not trajectory.points:<br \/>\n            return None<\/p>\n<p>        # \u63d0\u53d6\u4f4d\u7f6e\u70b9\u548c\u5c5e\u6027<br \/>\n        points &#061; np.array([p.position for p in trajectory.points])<\/p>\n<p>        # \u6839\u636e\u5c5e\u6027\u83b7\u53d6\u989c\u8272\u503c<br \/>\n        if color_by &#061;&#061; &#039;speed&#039;:<br \/>\n            scalars &#061; np.array([p.velocity for p in trajectory.points])<br \/>\n        elif color_by &#061;&#061; &#039;altitude&#039;:<br \/>\n            scalars &#061; np.array([p.position[2] for p in trajectory.points])<br \/>\n        elif color_by &#061;&#061; &#039;time&#039;:<br \/>\n            # \u5f52\u4e00\u5316\u65f6\u95f4<br \/>\n            times &#061; np.array([p.timestamp for p in trajectory.points])<br \/>\n            if isinstance(times[0], (int, float)):<br \/>\n                scalars &#061; (times &#8211; times.min()) \/ (times.max() &#8211; times.min() &#043; 1e-10)<br \/>\n            else:<br \/>\n                # \u5982\u679c\u662fdatetime&#xff0c;\u8f6c\u6362\u4e3a\u65f6\u95f4\u6233<br \/>\n                timestamps &#061; np.array([t.timestamp() for t in times])<br \/>\n                scalars &#061; (timestamps &#8211; timestamps.min()) \/ (timestamps.max() &#8211; timestamps.min() &#043; 1e-10)<br \/>\n        elif color_by &#061;&#061; &#039;distance&#039;:<br \/>\n            # \u8ba1\u7b97\u7d2f\u79ef\u8ddd\u79bb<br \/>\n            distances &#061; [0]<br \/>\n            for i in range(1, len(points)):<br \/>\n                dist &#061; np.linalg.norm(points[i] &#8211; points[i-1])<br \/>\n                distances.append(distances[-1] &#043; dist)<br \/>\n            scalars &#061; np.array(distances)<br \/>\n        else:<br \/>\n            scalars &#061; np.linspace(0, 1, len(points))<\/p>\n<p>        # \u521b\u5efa\u8f68\u8ff9\u51e0\u4f55<br \/>\n        if style &#061;&#061; &#039;line&#039;:<br \/>\n            mesh &#061; self.create_simple_line(points, **kwargs)<br \/>\n        elif style &#061;&#061; &#039;spline&#039;:<br \/>\n            mesh &#061; self.create_spline(points, **kwargs)<br \/>\n        elif style &#061;&#061; &#039;tube&#039;:<br \/>\n            mesh &#061; self.create_tube(points, **kwargs)<\/p>\n<p>        if mesh is None:<br \/>\n            return None<\/p>\n<p>        # \u6dfb\u52a0\u6807\u91cf\u6570\u636e<br \/>\n        # \u7531\u4e8e\u6837\u6761\u63d2\u503c&#xff0c;\u9700\u8981\u5c06\u6807\u91cf\u6570\u636e\u6620\u5c04\u5230\u65b0\u7684\u70b9<br \/>\n        if len(scalars) &gt; 0:<br \/>\n            # \u5bf9\u4e8e\u6837\u6761\u6216\u7ba1\u9053&#xff0c;\u9700\u8981\u63d2\u503c\u6807\u91cf<br \/>\n            if style in [&#039;spline&#039;, &#039;tube&#039;]:<br \/>\n                # \u521b\u5efa\u53c2\u6570\u5316\u8868\u793a<br \/>\n                t &#061; np.linspace(0, 1, len(points))<br \/>\n                t_new &#061; np.linspace(0, 1, len(mesh.points))<\/p>\n<p>                # \u7ebf\u6027\u63d2\u503c\u6807\u91cf<br \/>\n                from scipy import interpolate<br \/>\n                if len(t) &gt; 1:<br \/>\n                    interp_func &#061; interpolate.interp1d(t, scalars,<br \/>\n                                                     bounds_error&#061;False,<br \/>\n                                                     fill_value&#061;&#039;extrapolate&#039;)<br \/>\n                    mesh_scalars &#061; interp_func(t_new)<br \/>\n                else:<br \/>\n                    mesh_scalars &#061; np.ones(len(mesh.points)) * scalars[0]<br \/>\n            else:<br \/>\n                mesh_scalars &#061; scalars<\/p>\n<p>            mesh[color_by] &#061; mesh_scalars<\/p>\n<p>        return mesh<\/p>\n<p>    def add_color_mapped_trajectory(self, trajectory, color_by&#061;&#039;speed&#039;,<br \/>\n                                   colormap&#061;&#039;plasma&#039;, style&#061;&#039;tube&#039;, **kwargs):<br \/>\n        &#034;&#034;&#034;\u6dfb\u52a0\u989c\u8272\u6620\u5c04\u8f68\u8ff9\u5230\u573a\u666f&#034;&#034;&#034;<br \/>\n        mesh &#061; self.create_colored_trajectory(<br \/>\n            trajectory, color_by, colormap, style, **kwargs<br \/>\n        )<\/p>\n<p>        if mesh is None:<br \/>\n            return None<\/p>\n<p>        traj_id &#061; trajectory.id<br \/>\n        self.trajectory_meshes[traj_id] &#061; mesh<\/p>\n<p>        return mesh<\/p>\n<p>    def visualize_with_color_mapping(self, trajectories, color_by&#061;&#039;speed&#039;,<br \/>\n                                    colormap&#061;&#039;plasma&#039;, window_size&#061;(1400, 900)):<br \/>\n        &#034;&#034;&#034;\u53ef\u89c6\u5316\u5e26\u989c\u8272\u6620\u5c04\u7684\u8f68\u8ff9&#034;&#034;&#034;<br \/>\n        self.plotter &#061; pv.Plotter(window_size&#061;window_size,<br \/>\n                                 title&#061;f&#034;\u8f68\u8ff9\u53ef\u89c6\u5316 &#8211; \u989c\u8272\u6620\u5c04: {color_by}&#034;)<\/p>\n<p>        for trajectory in trajectories:<br \/>\n            # \u521b\u5efa\u989c\u8272\u6620\u5c04\u8f68\u8ff9<br \/>\n            mesh &#061; self.add_color_mapped_trajectory(<br \/>\n                trajectory,<br \/>\n                color_by&#061;color_by,<br \/>\n                colormap&#061;colormap,<br \/>\n                style&#061;&#039;tube&#039;,<br \/>\n                radius&#061;0.3<br \/>\n            )<\/p>\n<p>            if mesh and self.plotter:<br \/>\n                # \u6dfb\u52a0\u8f68\u8ff9&#xff08;\u5e26\u989c\u8272\u6620\u5c04&#xff09;<br \/>\n                self.plotter.add_mesh(<br \/>\n                    mesh,<br \/>\n                    scalars&#061;color_by,<br \/>\n                    cmap&#061;colormap,<br \/>\n                    opacity&#061;0.8,<br \/>\n                    clim&#061;[mesh[color_by].min(), mesh[color_by].max()],<br \/>\n                    show_scalar_bar&#061;True,<br \/>\n                    scalar_bar_args&#061;{&#039;title&#039;: f&#039;{color_by}&#039;},<br \/>\n                    name&#061;f&#039;trajectory_{trajectory.id}&#039;,<br \/>\n                    show_edges&#061;False<br \/>\n                )<\/p>\n<p>                # \u6dfb\u52a0\u8f68\u8ff9\u8d77\u59cb\u70b9<br \/>\n                if trajectory.points:<br \/>\n                    start_point &#061; trajectory.points[0].position<br \/>\n                    end_point &#061; trajectory.points[-1].position<\/p>\n<p>                    # \u8d77\u59cb\u70b9<br \/>\n                    start_mesh &#061; pv.Sphere(center&#061;start_point, radius&#061;0.5)<br \/>\n                    self.plotter.add_mesh(<br \/>\n                        start_mesh,<br \/>\n                        color&#061;&#039;green&#039;,<br \/>\n                        name&#061;f&#039;start_{trajectory.id}&#039;<br \/>\n                    )<\/p>\n<p>                    # \u7ec8\u70b9<br \/>\n                    end_mesh &#061; pv.Sphere(center&#061;end_point, radius&#061;0.5)<br \/>\n                    self.plotter.add_mesh(<br \/>\n                        end_mesh,<br \/>\n                        color&#061;&#039;red&#039;,<br \/>\n                        name&#061;f&#039;end_{trajectory.id}&#039;<br \/>\n                    )<\/p>\n<p>        # \u8bbe\u7f6e\u573a\u666f<br \/>\n        self.plotter.add_axes()<br \/>\n        self.plotter.show_grid()<\/p>\n<p>        # \u8bbe\u7f6e\u76f8\u673a<br \/>\n        self.plotter.camera_position &#061; [(100, 100, 100), (0, 0, 0), (0, 0, 1)]<br \/>\n        self.plotter.set_background(&#039;black&#039;)<\/p>\n<p>        # \u663e\u793a<br \/>\n        self.plotter.show()<\/p>\n<h3>4. \u6848\u4f8b1&#xff1a;\u76ee\u6807\u98de\u884c\u8f68\u8ff9\u53ef\u89c6\u5316<\/h3>\n<h4>4.1 \u6570\u636e\u751f\u6210\u4e0e\u52a0\u8f7d<\/h4>\n<p>\u9996\u5148&#xff0c;\u6211\u4eec\u521b\u5efa\u4e00\u4e2a\u6a21\u62df\u7684\u76ee\u6807\u98de\u884c\u8f68\u8ff9\u6570\u636e\u96c6&#xff1a;<\/p>\n<p>def generate_flight_trajectory(num_points&#061;100, trajectory_id&#061;&#039;001&#039;):<br \/>\n    &#034;&#034;&#034;\u751f\u6210\u6a21\u62df\u98de\u884c\u8f68\u8ff9\u6570\u636e&#034;&#034;&#034;<br \/>\n    from datetime import datetime, timedelta<\/p>\n<p>    # \u521d\u59cb\u53c2\u6570<br \/>\n    start_time &#061; datetime(2024, 1, 1, 12, 0, 0)<br \/>\n    start_pos &#061; np.array([0, 0, 1000])  # \u8d77\u59cb\u4f4d\u7f6e<br \/>\n    start_speed &#061; 200  # \u8d77\u59cb\u901f\u5ea6 (m\/s)<\/p>\n<p>    trajectory &#061; TrajectoryData.Trajectory(trajectory_id, &#039;aircraft&#039;)<\/p>\n<p>    # \u751f\u6210\u8f68\u8ff9\u70b9<br \/>\n    for i in range(num_points):<br \/>\n        t &#061; i * 10  # 10\u79d2\u95f4\u9694<\/p>\n<p>        # \u8ba1\u7b97\u4f4d\u7f6e&#xff08;\u5706\u5468\u8fd0\u52a8 &#043; \u722c\u5347&#xff09;<br \/>\n        angle &#061; math.radians(t * 2)  # \u6bcf10\u79d2\u8f6c2\u5ea6<br \/>\n        radius &#061; 5000  # \u534a\u5f84<\/p>\n<p>        x &#061; start_pos[0] &#043; radius * math.cos(angle)<br \/>\n        y &#061; start_pos[1] &#043; radius * math.sin(angle)<br \/>\n        z &#061; start_pos[2] &#043; t * 5  # \u6bcf10\u79d2\u722c\u534750\u7c73<\/p>\n<p>        # \u8ba1\u7b97\u901f\u5ea6&#xff08;\u9010\u6e10\u52a0\u901f&#xff09;<br \/>\n        speed &#061; start_speed &#043; t * 0.2  # \u6bcf\u79d2\u52a0\u901f0.2m\/s<\/p>\n<p>        # \u8ba1\u7b97\u822a\u5411<br \/>\n        heading &#061; math.degrees(angle &#043; math.pi\/2)  # \u5207\u7ebf\u65b9\u5411<\/p>\n<p>        # \u521b\u5efa\u8f68\u8ff9\u70b9<br \/>\n        timestamp &#061; start_time &#043; timedelta(seconds&#061;t)<br \/>\n        point &#061; TrajectoryData.TrajectoryPoint(<br \/>\n            timestamp&#061;timestamp,<br \/>\n            position&#061;[x, y, z],<br \/>\n            velocity&#061;speed,<br \/>\n            heading&#061;heading<br \/>\n        )<\/p>\n<p>        trajectory.add_point(point)<\/p>\n<p>    return trajectory<\/p>\n<p>def save_trajectory_to_csv(trajectory, filename&#061;&#039;flight_trajectory.csv&#039;):<br \/>\n    &#034;&#034;&#034;\u4fdd\u5b58\u8f68\u8ff9\u5230CSV\u6587\u4ef6&#034;&#034;&#034;<br \/>\n    data &#061; []<\/p>\n<p>    for point in trajectory.points:<br \/>\n        row &#061; {<br \/>\n            &#039;timestamp&#039;: point.timestamp,<br \/>\n            &#039;object_id&#039;: trajectory.id,<br \/>\n            &#039;x&#039;: point.position[0],<br \/>\n            &#039;y&#039;: point.position[1],<br \/>\n            &#039;z&#039;: point.position[2],<br \/>\n            &#039;speed&#039;: point.velocity,<br \/>\n            &#039;heading&#039;: point.heading<br \/>\n        }<br \/>\n        data.append(row)<\/p>\n<p>    df &#061; pd.DataFrame(data)<br \/>\n    df.to_csv(filename, index&#061;False)<br \/>\n    print(f&#034;\u8f68\u8ff9\u5df2\u4fdd\u5b58\u5230: {filename}&#034;)<\/p>\n<p>    return df<\/p>\n<h4>4.2 \u5b8c\u6574\u7684\u6848\u4f8b1\u5b9e\u73b0<\/h4>\n<p>\u73b0\u5728&#xff0c;\u6211\u4eec\u5b9e\u73b0\u4e00\u4e2a\u5b8c\u6574\u7684\u98de\u884c\u8f68\u8ff9\u53ef\u89c6\u5316\u6848\u4f8b&#xff1a;<\/p>\n<p>class FlightTrajectoryDemo:<br \/>\n    &#034;&#034;&#034;\u98de\u884c\u8f68\u8ff9\u6f14\u793a\u7c7b&#034;&#034;&#034;<\/p>\n<p>    def __init__(self):<br \/>\n        self.trajectory_data &#061; TrajectoryData()<br \/>\n        self.visualizer &#061; EnhancedTrajectoryVisualizer()<\/p>\n<p>    def load_or_generate_data(self):<br \/>\n        &#034;&#034;&#034;\u52a0\u8f7d\u6216\u751f\u6210\u8f68\u8ff9\u6570\u636e&#034;&#034;&#034;<br \/>\n        # \u5c1d\u8bd5\u4ece\u6587\u4ef6\u52a0\u8f7d<br \/>\n        try:<br \/>\n            self.trajectory_data.load_from_csv(<br \/>\n                &#039;flight_trajectory.csv&#039;,<br \/>\n                time_column&#061;&#039;timestamp&#039;,<br \/>\n                id_column&#061;&#039;object_id&#039;,<br \/>\n                x_column&#061;&#039;x&#039;,<br \/>\n                y_column&#061;&#039;y&#039;,<br \/>\n                z_column&#061;&#039;z&#039;<br \/>\n            )<br \/>\n            print(&#034;\u4ece\u6587\u4ef6\u52a0\u8f7d\u8f68\u8ff9\u6570\u636e\u6210\u529f&#034;)<br \/>\n        except FileNotFoundError:<br \/>\n            print(&#034;\u672a\u627e\u5230\u8f68\u8ff9\u6587\u4ef6&#xff0c;\u751f\u6210\u6a21\u62df\u6570\u636e&#8230;&#034;)<br \/>\n            # \u751f\u6210\u6a21\u62df\u8f68\u8ff9<br \/>\n            trajectory &#061; generate_flight_trajectory(100, &#039;001&#039;)<br \/>\n            self.trajectory_data.trajectories[&#039;001&#039;] &#061; trajectory<br \/>\n            # \u4fdd\u5b58\u5230\u6587\u4ef6<br \/>\n            save_trajectory_to_csv(trajectory)<\/p>\n<p>        return self.trajectory_data.get_summary()<\/p>\n<p>    def create_terrain(self, plotter):<br \/>\n        &#034;&#034;&#034;\u521b\u5efa\u5730\u5f62&#034;&#034;&#034;<br \/>\n        # \u521b\u5efa\u7b80\u5355\u5730\u5f62\u7f51\u683c<br \/>\n        x &#061; np.linspace(-10000, 10000, 50)<br \/>\n        y &#061; np.linspace(-10000, 10000, 50)<br \/>\n        xx, yy &#061; np.meshgrid(x, y)<\/p>\n<p>        # \u521b\u5efa\u8d77\u4f0f\u5730\u5f62<br \/>\n        z &#061; 1000 &#043; 200 * np.sin(0.0005 * xx) * np.cos(0.0005 * yy)<\/p>\n<p>        terrain &#061; pv.StructuredGrid(xx, yy, z)<br \/>\n        terrain[&#039;elevation&#039;] &#061; z.ravel()<\/p>\n<p>        plotter.add_mesh(<br \/>\n            terrain,<br \/>\n            cmap&#061;&#039;terrain&#039;,<br \/>\n            scalars&#061;&#039;elevation&#039;,<br \/>\n            opacity&#061;0.7,<br \/>\n            show_edges&#061;False,<br \/>\n            name&#061;&#039;terrain&#039;<br \/>\n        )<\/p>\n<p>        return terrain<\/p>\n<p>    def add_aircraft_model(self, plotter, position, scale&#061;10.0, color&#061;&#039;white&#039;):<br \/>\n        &#034;&#034;&#034;\u6dfb\u52a0\u98de\u673a\u6a21\u578b&#034;&#034;&#034;<br \/>\n        # \u7b80\u5316\u98de\u673a\u6a21\u578b<br \/>\n        fuselage &#061; pv.Cylinder(center&#061;[0, 0, 0], direction&#061;[1, 0, 0],<br \/>\n                              radius&#061;1*scale, height&#061;6*scale)<br \/>\n        wing &#061; pv.Box(bounds&#061;[-0.5*scale, 0.5*scale, -4*scale, 4*scale,<br \/>\n                             -0.2*scale, 0.2*scale])<br \/>\n        tail &#061; pv.Box(bounds&#061;[-3*scale, -2*scale, -1.5*scale, 1.5*scale,<br \/>\n                             -0.5*scale, 0.5*scale])<\/p>\n<p>        aircraft &#061; fuselage.boolean_union(wing)<br \/>\n        aircraft &#061; aircraft.boolean_union(tail)<\/p>\n<p>        # \u5b9a\u4f4d<br \/>\n        aircraft.translate(position, inplace&#061;True)<\/p>\n<p>        plotter.add_mesh(aircraft, color&#061;color, name&#061;&#039;aircraft_model&#039;)<\/p>\n<p>        return aircraft<\/p>\n<p>    def add_info_panel(self, plotter, trajectory):<br \/>\n        &#034;&#034;&#034;\u6dfb\u52a0\u4fe1\u606f\u9762\u677f&#034;&#034;&#034;<br \/>\n        if not trajectory.points:<br \/>\n            return<\/p>\n<p>        # \u8f68\u8ff9\u7edf\u8ba1<br \/>\n        stats &#061; trajectory.get_statistics()<\/p>\n<p>        info_text &#061; f&#034;\u98de\u884c\u8f68\u8ff9\u4fe1\u606f\\\\n&#034;<br \/>\n        info_text &#043;&#061; f&#034;\u76ee\u6807ID: {trajectory.id}\\\\n&#034;<br \/>\n        info_text &#043;&#061; f&#034;\u76ee\u6807\u7c7b\u578b: {trajectory.object_type}\\\\n&#034;<br \/>\n        info_text &#043;&#061; f&#034;\u8f68\u8ff9\u70b9\u6570: {stats[&#039;point_count&#039;]}\\\\n&#034;<br \/>\n        info_text &#043;&#061; f&#034;\u6301\u7eed\u65f6\u95f4: {stats[&#039;duration&#039;]:.1f}s\\\\n&#034;<br \/>\n        info_text &#043;&#061; f&#034;\u603b\u8ddd\u79bb: {stats[&#039;total_distance&#039;]:.1f}m\\\\n&#034;<br \/>\n        info_text &#043;&#061; f&#034;\u5e73\u5747\u901f\u5ea6: {stats[&#039;avg_speed&#039;]:.1f}m\/s\\\\n&#034;<br \/>\n        info_text &#043;&#061; f&#034;\u6700\u5927\u901f\u5ea6: {stats[&#039;max_speed&#039;]:.1f}m\/s\\\\n&#034;<br \/>\n        info_text &#043;&#061; f&#034;\u9ad8\u5ea6\u8303\u56f4: {stats[&#039;min_altitude&#039;]:.1f}-{stats[&#039;max_altitude&#039;]:.1f}m\\\\n&#034;<\/p>\n<p>        # \u6dfb\u52a0\u6587\u672c<br \/>\n        plotter.add_text(<br \/>\n            info_text,<br \/>\n            position&#061;&#039;upper_left&#039;,<br \/>\n            font_size&#061;10,<br \/>\n            color&#061;&#039;white&#039;,<br \/>\n            name&#061;&#039;info_panel&#039;<br \/>\n        )<\/p>\n<p>    def add_time_slider(self, plotter, trajectory):<br \/>\n        &#034;&#034;&#034;\u6dfb\u52a0\u65f6\u95f4\u6ed1\u5757&#034;&#034;&#034;<br \/>\n        if not trajectory.points:<br \/>\n            return<\/p>\n<p>        # \u521b\u5efa\u65f6\u95f4\u70b9<br \/>\n        times &#061; [p.timestamp for p in trajectory.points]<\/p>\n<p>        # \u5982\u679c\u662fdatetime&#xff0c;\u8f6c\u6362\u4e3a\u65f6\u95f4\u6233<br \/>\n        if isinstance(times[0], datetime):<br \/>\n            time_values &#061; [t.timestamp() for t in times]<br \/>\n        else:<br \/>\n            time_values &#061; times<\/p>\n<p>        time_min &#061; min(time_values)<br \/>\n        time_max &#061; max(time_values)<\/p>\n<p>        # \u521b\u5efa\u6ed1\u5757<br \/>\n        def update_time(value):<br \/>\n            # \u627e\u5230\u6700\u63a5\u8fd1\u7684\u65f6\u95f4\u70b9<br \/>\n            idx &#061; np.argmin(np.abs(np.array(time_values) &#8211; value))<br \/>\n            if 0 &lt;&#061; idx &lt; len(trajectory.points):<br \/>\n                point &#061; trajectory.points[idx]<\/p>\n<p>                # \u66f4\u65b0\u98de\u673a\u4f4d\u7f6e<br \/>\n                if &#039;aircraft_model&#039; in plotter.actors:<br \/>\n                    plotter.remove_actor(plotter.actors[&#039;aircraft_model&#039;])<\/p>\n<p>                self.add_aircraft_model(plotter, point.position)<\/p>\n<p>                # \u66f4\u65b0\u65f6\u95f4\u6587\u672c<br \/>\n                time_text &#061; f&#034;\u65f6\u95f4: {point.timestamp}\\\\n&#034;<br \/>\n                time_text &#043;&#061; f&#034;\u4f4d\u7f6e: {point.position}\\\\n&#034;<br \/>\n                time_text &#043;&#061; f&#034;\u901f\u5ea6: {point.velocity:.1f}m\/s\\\\n&#034;<br \/>\n                time_text &#043;&#061; f&#034;\u822a\u5411: {point.heading:.1f}\u00b0&#034;<\/p>\n<p>                if &#039;time_info&#039; in plotter.actors:<br \/>\n                    plotter.remove_actor(plotter.actors[&#039;time_info&#039;])<\/p>\n<p>                plotter.add_text(<br \/>\n                    time_text,<br \/>\n                    position&#061;&#039;upper_right&#039;,<br \/>\n                    font_size&#061;10,<br \/>\n                    color&#061;&#039;yellow&#039;,<br \/>\n                    name&#061;&#039;time_info&#039;<br \/>\n                )<\/p>\n<p>        # \u6dfb\u52a0\u6ed1\u5757<br \/>\n        plotter.add_slider_widget(<br \/>\n            update_time,<br \/>\n            [time_min, time_max],<br \/>\n            value&#061;time_min,<br \/>\n            title&#061;&#039;\u65f6\u95f4&#039;,<br \/>\n            pointa&#061;(0.1, 0.9),<br \/>\n            pointb&#061;(0.4, 0.9),<br \/>\n            style&#061;&#039;modern&#039;<br \/>\n        )<\/p>\n<p>    def run_demo(self, color_by&#061;&#039;speed&#039;):<br \/>\n        &#034;&#034;&#034;\u8fd0\u884c\u6f14\u793a&#034;&#034;&#034;<br \/>\n        print(&#034;\u98de\u884c\u8f68\u8ff9\u53ef\u89c6\u5316\u6f14\u793a&#034;)<br \/>\n        print(&#034;&#061;&#034; * 50)<\/p>\n<p>        # \u52a0\u8f7d\u6570\u636e<br \/>\n        summary &#061; self.load_or_generate_data()<br \/>\n        print(f&#034;\u6570\u636e\u6458\u8981: {summary}&#034;)<\/p>\n<p>        # \u83b7\u53d6\u8f68\u8ff9<br \/>\n        trajectories &#061; self.trajectory_data.get_all_trajectories()<br \/>\n        if not trajectories:<br \/>\n            print(&#034;\u6ca1\u6709\u627e\u5230\u8f68\u8ff9\u6570\u636e&#034;)<br \/>\n            return<\/p>\n<p>        trajectory &#061; trajectories[0]<\/p>\n<p>        # \u521b\u5efa\u53ef\u89c6\u5316\u7a97\u53e3<br \/>\n        plotter &#061; pv.Plotter(window_size&#061;(1600, 1000),<br \/>\n                            title&#061;f&#034;\u98de\u884c\u8f68\u8ff9\u53ef\u89c6\u5316 &#8211; \u989c\u8272\u6620\u5c04: {color_by}&#034;)<\/p>\n<p>        # \u6dfb\u52a0\u5730\u5f62<br \/>\n        self.create_terrain(plotter)<\/p>\n<p>        # \u6dfb\u52a0\u8f68\u8ff9&#xff08;\u5e26\u989c\u8272\u6620\u5c04&#xff09;<br \/>\n        mesh &#061; self.visualizer.create_colored_trajectory(<br \/>\n            trajectory,<br \/>\n            color_by&#061;color_by,<br \/>\n            colormap&#061;&#039;plasma&#039;,<br \/>\n            style&#061;&#039;tube&#039;,<br \/>\n            radius&#061;50<br \/>\n        )<\/p>\n<p>        if mesh:<br \/>\n            plotter.add_mesh(<br \/>\n                mesh,<br \/>\n                scalars&#061;color_by,<br \/>\n                cmap&#061;&#039;plasma&#039;,<br \/>\n                opacity&#061;0.8,<br \/>\n                clim&#061;[mesh[color_by].min(), mesh[color_by].max()],<br \/>\n                show_scalar_bar&#061;True,<br \/>\n                scalar_bar_args&#061;{<br \/>\n                    &#039;title&#039;: f&#039;{color_by}&#039;,<br \/>\n                    &#039;vertical&#039;: True,<br \/>\n                    &#039;position_x&#039;: 0.85,<br \/>\n                    &#039;position_y&#039;: 0.3,<br \/>\n                    &#039;height&#039;: 0.4<br \/>\n                },<br \/>\n                name&#061;&#039;trajectory&#039;,<br \/>\n                show_edges&#061;False<br \/>\n            )<\/p>\n<p>        # \u6dfb\u52a0\u8f68\u8ff9\u8d77\u59cb\u70b9<br \/>\n        if trajectory.points:<br \/>\n            # \u8d77\u59cb\u70b9<br \/>\n            start_point &#061; trajectory.points[0].position<br \/>\n            start_mesh &#061; pv.Sphere(center&#061;start_point, radius&#061;100)<br \/>\n            plotter.add_mesh(<br \/>\n                start_mesh,<br \/>\n                color&#061;&#039;green&#039;,<br \/>\n                name&#061;&#039;start_point&#039;<br \/>\n            )<\/p>\n<p>            # \u7ec8\u70b9<br \/>\n            end_point &#061; trajectory.points[-1].position<br \/>\n            end_mesh &#061; pv.Sphere(center&#061;end_point, radius&#061;100)<br \/>\n            plotter.add_mesh(<br \/>\n                end_mesh,<br \/>\n                color&#061;&#039;red&#039;,<br \/>\n                name&#061;&#039;end_point&#039;<br \/>\n            )<\/p>\n<p>            # \u6dfb\u52a0\u98de\u673a\u6a21\u578b&#xff08;\u5728\u8d77\u59cb\u4f4d\u7f6e&#xff09;<br \/>\n            self.add_aircraft_model(plotter, start_point, scale&#061;20)<\/p>\n<p>        # \u6dfb\u52a0\u8f68\u8ff9\u70b9&#xff08;\u91c7\u6837\u663e\u793a&#xff09;<br \/>\n        points &#061; np.array([p.position for p in trajectory.points])<br \/>\n        if len(points) &gt; 0:<br \/>\n            # \u5747\u5300\u91c7\u6837<br \/>\n            sample_indices &#061; np.linspace(0, len(points)-1, 20, dtype&#061;int)<br \/>\n            sample_points &#061; points[sample_indices]<\/p>\n<p>            points_mesh &#061; pv.PolyData(sample_points)<br \/>\n            plotter.add_mesh(<br \/>\n                points_mesh,<br \/>\n                color&#061;&#039;white&#039;,<br \/>\n                point_size&#061;10,<br \/>\n                render_points_as_spheres&#061;True,<br \/>\n                name&#061;&#039;trajectory_points&#039;<br \/>\n            )<\/p>\n<p>            # \u6dfb\u52a0\u70b9\u6807\u7b7e<br \/>\n            for i, idx in enumerate(sample_indices):<br \/>\n                if idx &lt; len(trajectory.points):<br \/>\n                    point &#061; trajectory.points[idx]<br \/>\n                    label_text &#061; f&#034;t&#061;{point.timestamp if isinstance(point.timestamp, (int, float)) else point.timestamp.strftime(&#039;%H:%M:%S&#039;)}&#034;<br \/>\n                    plotter.add_point_labels(<br \/>\n                        [point.position],<br \/>\n                        [label_text],<br \/>\n                        font_size&#061;8,<br \/>\n                        point_color&#061;&#039;white&#039;,<br \/>\n                        point_size&#061;0,<br \/>\n                        name&#061;f&#039;label_{i}&#039;<br \/>\n                    )<\/p>\n<p>        # \u6dfb\u52a0\u4fe1\u606f\u9762\u677f<br \/>\n        self.add_info_panel(plotter, trajectory)<\/p>\n<p>        # \u6dfb\u52a0\u65f6\u95f4\u6ed1\u5757<br \/>\n        self.add_time_slider(plotter, trajectory)<\/p>\n<p>        # \u8bbe\u7f6e\u573a\u666f<br \/>\n        plotter.add_axes()<br \/>\n        plotter.show_grid()<\/p>\n<p>        # \u8bbe\u7f6e\u76f8\u673a<br \/>\n        plotter.camera_position &#061; [(20000, 20000, 10000), (0, 0, 5000), (0, 0, 1)]<br \/>\n        plotter.set_background(&#039;linear_gradient&#039;, bottom&#061;&#039;#0a0a2a&#039;, top&#061;&#039;#1a1a3a&#039;)<\/p>\n<p>        # \u6dfb\u52a0\u63a7\u5236\u8bf4\u660e<br \/>\n        controls &#061; &#034;\u63a7\u5236\u8bf4\u660e:\\\\n&#034;<br \/>\n        controls &#043;&#061; &#034;\u9f20\u6807\u62d6\u62fd: \u65cb\u8f6c\u89c6\u89d2\\\\n&#034;<br \/>\n        controls &#043;&#061; &#034;\u9f20\u6807\u53f3\u952e\u62d6\u62fd: \u5e73\u79fb\u89c6\u89d2\\\\n&#034;<br \/>\n        controls &#043;&#061; &#034;\u9f20\u6807\u6eda\u8f6e: \u7f29\u653e\\\\n&#034;<br \/>\n        controls &#043;&#061; &#034;R\u952e: \u91cd\u7f6e\u89c6\u89d2\\\\n&#034;<br \/>\n        controls &#043;&#061; &#034;1\u952e: \u6309\u901f\u5ea6\u7740\u8272\\\\n&#034;<br \/>\n        controls &#043;&#061; &#034;2\u952e: \u6309\u9ad8\u5ea6\u7740\u8272\\\\n&#034;<br \/>\n        controls &#043;&#061; &#034;3\u952e: \u6309\u65f6\u95f4\u7740\u8272\\\\n&#034;<br \/>\n        controls &#043;&#061; &#034;4\u952e: \u6309\u8ddd\u79bb\u7740\u8272\\\\n&#034;<\/p>\n<p>        plotter.add_text(<br \/>\n            controls,<br \/>\n            position&#061;&#039;lower_left&#039;,<br \/>\n            font_size&#061;10,<br \/>\n            color&#061;&#039;cyan&#039;,<br \/>\n            name&#061;&#039;controls&#039;<br \/>\n        )<\/p>\n<p>        # \u6dfb\u52a0\u952e\u76d8\u4e8b\u4ef6<br \/>\n        def set_color_by_speed():<br \/>\n            plotter.remove_actor(plotter.actors[&#039;trajectory&#039;])<br \/>\n            mesh &#061; self.visualizer.create_colored_trajectory(<br \/>\n                trajectory,<br \/>\n                color_by&#061;&#039;speed&#039;,<br \/>\n                colormap&#061;&#039;plasma&#039;,<br \/>\n                style&#061;&#039;tube&#039;,<br \/>\n                radius&#061;50<br \/>\n            )<br \/>\n            plotter.add_mesh(<br \/>\n                mesh,<br \/>\n                scalars&#061;&#039;speed&#039;,<br \/>\n                cmap&#061;&#039;plasma&#039;,<br \/>\n                opacity&#061;0.8,<br \/>\n                name&#061;&#039;trajectory&#039;,<br \/>\n                show_edges&#061;False<br \/>\n            )<br \/>\n            print(&#034;\u989c\u8272\u6620\u5c04: \u901f\u5ea6&#034;)<\/p>\n<p>        def set_color_by_altitude():<br \/>\n            plotter.remove_actor(plotter.actors[&#039;trajectory&#039;])<br \/>\n            mesh &#061; self.visualizer.create_colored_trajectory(<br \/>\n                trajectory,<br \/>\n                color_by&#061;&#039;altitude&#039;,<br \/>\n                colormap&#061;&#039;terrain&#039;,<br \/>\n                style&#061;&#039;tube&#039;,<br \/>\n                radius&#061;50<br \/>\n            )<br \/>\n            plotter.add_mesh(<br \/>\n                mesh,<br \/>\n                scalars&#061;&#039;altitude&#039;,<br \/>\n                cmap&#061;&#039;terrain&#039;,<br \/>\n                opacity&#061;0.8,<br \/>\n                name&#061;&#039;trajectory&#039;,<br \/>\n                show_edges&#061;False<br \/>\n            )<br \/>\n            print(&#034;\u989c\u8272\u6620\u5c04: \u9ad8\u5ea6&#034;)<\/p>\n<p>        def set_color_by_time():<br \/>\n            plotter.remove_actor(plotter.actors[&#039;trajectory&#039;])<br \/>\n            mesh &#061; self.visualizer.create_colored_trajectory(<br \/>\n                trajectory,<br \/>\n                color_by&#061;&#039;time&#039;,<br \/>\n                colormap&#061;&#039;viridis&#039;,<br \/>\n                style&#061;&#039;tube&#039;,<br \/>\n                radius&#061;50<br \/>\n            )<br \/>\n            plotter.add_mesh(<br \/>\n                mesh,<br \/>\n                scalars&#061;&#039;time&#039;,<br \/>\n                cmap&#061;&#039;viridis&#039;,<br \/>\n                opacity&#061;0.8,<br \/>\n                name&#061;&#039;trajectory&#039;,<br \/>\n                show_edges&#061;False<br \/>\n            )<br \/>\n            print(&#034;\u989c\u8272\u6620\u5c04: \u65f6\u95f4&#034;)<\/p>\n<p>        def set_color_by_distance():<br \/>\n            plotter.remove_actor(plotter.actors[&#039;trajectory&#039;])<br \/>\n            mesh &#061; self.visualizer.create_colored_trajectory(<br \/>\n                trajectory,<br \/>\n                color_by&#061;&#039;distance&#039;,<br \/>\n                colormap&#061;&#039;hot&#039;,<br \/>\n                style&#061;&#039;tube&#039;,<br \/>\n                radius&#061;50<br \/>\n            )<br \/>\n            plotter.add_mesh(<br \/>\n                mesh,<br \/>\n                scalars&#061;&#039;distance&#039;,<br \/>\n                cmap&#061;&#039;hot&#039;,<br \/>\n                opacity&#061;0.8,<br \/>\n                name&#061;&#039;trajectory&#039;,<br \/>\n                show_edges&#061;False<br \/>\n            )<br \/>\n            print(&#034;\u989c\u8272\u6620\u5c04: \u8ddd\u79bb&#034;)<\/p>\n<p>        plotter.add_key_event(&#034;1&#034;, set_color_by_speed)<br \/>\n        plotter.add_key_event(&#034;2&#034;, set_color_by_altitude)<br \/>\n        plotter.add_key_event(&#034;3&#034;, set_color_by_time)<br \/>\n        plotter.add_key_event(&#034;4&#034;, set_color_by_distance)<\/p>\n<p>        print(&#034;\\\\n\u6f14\u793a\u5df2\u542f\u52a8&#034;)<br \/>\n        print(&#034;\u4f7f\u7528\u952e\u76d81-4\u952e\u5207\u6362\u989c\u8272\u6620\u5c04&#034;)<\/p>\n<p>        # \u663e\u793a<br \/>\n        plotter.show()<\/p>\n<p># \u8fd0\u884c\u6848\u4f8b1<br \/>\ndef run_case1():<br \/>\n    demo &#061; FlightTrajectoryDemo()<br \/>\n    demo.run_demo(color_by&#061;&#039;speed&#039;)<\/p>\n<p>if __name__ &#061;&#061; &#034;__main__&#034;:<br \/>\n    run_case1()<\/p>\n<h3>5. \u6848\u4f8b2&#xff1a;\u591a\u76ee\u6807\u8f68\u8ff9\u5bf9\u6bd4\u5206\u6790<\/h3>\n<h4>5.1 \u591a\u76ee\u6807\u6570\u636e\u751f\u6210<\/h4>\n<p>\u521b\u5efa\u591a\u4e2a\u76ee\u6807\u7684\u8f68\u8ff9\u6570\u636e\u7528\u4e8e\u5bf9\u6bd4\u5206\u6790&#xff1a;<\/p>\n<p>def generate_multiple_trajectories(num_trajectories&#061;3, points_per_trajectory&#061;50):<br \/>\n    &#034;&#034;&#034;\u751f\u6210\u591a\u4e2a\u76ee\u6807\u7684\u8f68\u8ff9\u6570\u636e&#034;&#034;&#034;<br \/>\n    trajectories &#061; []<\/p>\n<p>    for i in range(num_trajectories):<br \/>\n        traj_id &#061; f&#034;T{i&#043;1:03d}&#034;<\/p>\n<p>        # \u4e0d\u540c\u7c7b\u578b\u7684\u8f68\u8ff9<br \/>\n        if i &#061;&#061; 0:<br \/>\n            # \u76f4\u7ebf\u98de\u884c<br \/>\n            trajectory &#061; generate_straight_trajectory(traj_id, points_per_trajectory)<br \/>\n        elif i &#061;&#061; 1:<br \/>\n            # \u5706\u5468\u98de\u884c<br \/>\n            trajectory &#061; generate_circular_trajectory(traj_id, points_per_trajectory)<br \/>\n        else:<br \/>\n            # \u968f\u673a\u98de\u884c<br \/>\n            trajectory &#061; generate_random_trajectory(traj_id, points_per_trajectory)<\/p>\n<p>        trajectories.append(trajectory)<\/p>\n<p>    return trajectories<\/p>\n<p>def generate_straight_trajectory(traj_id, num_points):<br \/>\n    &#034;&#034;&#034;\u751f\u6210\u76f4\u7ebf\u98de\u884c\u8f68\u8ff9&#034;&#034;&#034;<br \/>\n    from datetime import datetime, timedelta<\/p>\n<p>    start_time &#061; datetime(2024, 1, 1, 12, 0, 0)<br \/>\n    start_pos &#061; np.array([-5000, -5000, 1000])<\/p>\n<p>    trajectory &#061; TrajectoryData.Trajectory(traj_id, &#039;aircraft&#039;)<\/p>\n<p>    for i in range(num_points):<br \/>\n        t &#061; i * 10<br \/>\n        x &#061; start_pos[0] &#043; t * 20<br \/>\n        y &#061; start_pos[1] &#043; t * 15<br \/>\n        z &#061; start_pos[2] &#043; t * 2<\/p>\n<p>        timestamp &#061; start_time &#043; timedelta(seconds&#061;t)<br \/>\n        point &#061; TrajectoryData.TrajectoryPoint(<br \/>\n            timestamp&#061;timestamp,<br \/>\n            position&#061;[x, y, z],<br \/>\n            velocity&#061;200 &#043; i * 0.5,<br \/>\n            heading&#061;45<br \/>\n        )<\/p>\n<p>        trajectory.add_point(point)<\/p>\n<p>    return trajectory<\/p>\n<p>def generate_circular_trajectory(traj_id, num_points):<br \/>\n    &#034;&#034;&#034;\u751f\u6210\u5706\u5468\u98de\u884c\u8f68\u8ff9&#034;&#034;&#034;<br \/>\n    from datetime import datetime, timedelta<\/p>\n<p>    start_time &#061; datetime(2024, 1, 1, 12, 0, 0)<br \/>\n    center &#061; np.array([0, 0, 2000])<br \/>\n    radius &#061; 3000<\/p>\n<p>    trajectory &#061; TrajectoryData.Trajectory(traj_id, &#039;aircraft&#039;)<\/p>\n<p>    for i in range(num_points):<br \/>\n        t &#061; i * 10<br \/>\n        angle &#061; math.radians(t * 3)  # \u6bcf10\u79d2\u8f6c3\u5ea6<\/p>\n<p>        x &#061; center[0] &#043; radius * math.cos(angle)<br \/>\n        y &#061; center[1] &#043; radius * math.sin(angle)<br \/>\n        z &#061; center[2] &#043; math.sin(angle * 2) * 500<\/p>\n<p>        # \u8ba1\u7b97\u901f\u5ea6&#xff08;\u5706\u5468\u8fd0\u52a8\u901f\u5ea6&#xff09;<br \/>\n        angular_speed &#061; math.radians(3)  # \u5f27\u5ea6\/\u79d2<br \/>\n        speed &#061; radius * angular_speed<\/p>\n<p>        # \u8ba1\u7b97\u822a\u5411&#xff08;\u5207\u7ebf\u65b9\u5411&#xff09;<br \/>\n        heading &#061; math.degrees(angle &#043; math.pi\/2)<\/p>\n<p>        timestamp &#061; start_time &#043; timedelta(seconds&#061;t)<br \/>\n        point &#061; TrajectoryData.TrajectoryPoint(<br \/>\n            timestamp&#061;timestamp,<br \/>\n            position&#061;[x, y, z],<br \/>\n            velocity&#061;speed,<br \/>\n            heading&#061;heading<br \/>\n        )<\/p>\n<p>        trajectory.add_point(point)<\/p>\n<p>    return trajectory<\/p>\n<p>def generate_random_trajectory(traj_id, num_points):<br \/>\n    &#034;&#034;&#034;\u751f\u6210\u968f\u673a\u98de\u884c\u8f68\u8ff9&#034;&#034;&#034;<br \/>\n    from datetime import datetime, timedelta<br \/>\n    import random<\/p>\n<p>    start_time &#061; datetime(2024, 1, 1, 12, 0, 0)<br \/>\n    start_pos &#061; np.array([5000, 5000, 1500])<\/p>\n<p>    trajectory &#061; TrajectoryData.Trajectory(traj_id, &#039;aircraft&#039;)<\/p>\n<p>    # \u968f\u673a\u884c\u8d70<br \/>\n    current_pos &#061; start_pos.copy()<br \/>\n    current_heading &#061; 0<br \/>\n    current_speed &#061; 180<\/p>\n<p>    for i in range(num_points):<br \/>\n        t &#061; i * 10<\/p>\n<p>        # \u968f\u673a\u53d8\u5316<br \/>\n        heading_change &#061; random.uniform(-10, 10)<br \/>\n        speed_change &#061; random.uniform(-5, 5)<br \/>\n        altitude_change &#061; random.uniform(-20, 20)<\/p>\n<p>        current_heading &#043;&#061; heading_change<br \/>\n        current_speed &#061; max(100, min(300, current_speed &#043; speed_change))<\/p>\n<p>        # \u8ba1\u7b97\u65b0\u4f4d\u7f6e<br \/>\n        heading_rad &#061; math.radians(current_heading)<br \/>\n        dx &#061; current_speed * 10 * math.cos(heading_rad)<br \/>\n        dy &#061; current_speed * 10 * math.sin(heading_rad)<br \/>\n        dz &#061; altitude_change * 10<\/p>\n<p>        new_pos &#061; current_pos &#043; np.array([dx, dy, dz])<br \/>\n        current_pos &#061; new_pos<\/p>\n<p>        timestamp &#061; start_time &#043; timedelta(seconds&#061;t)<br \/>\n        point &#061; TrajectoryData.TrajectoryPoint(<br \/>\n            timestamp&#061;timestamp,<br \/>\n            position&#061;new_pos.tolist(),<br \/>\n            velocity&#061;current_speed,<br \/>\n            heading&#061;current_heading<br \/>\n        )<\/p>\n<p>        trajectory.add_point(point)<\/p>\n<p>    return trajectory<\/p>\n<h4>5.2 \u5b8c\u6574\u7684\u6848\u4f8b2\u5b9e\u73b0<\/h4>\n<p>\u5b9e\u73b0\u591a\u76ee\u6807\u8f68\u8ff9\u5bf9\u6bd4\u5206\u6790\u7684\u53ef\u89c6\u5316\u7cfb\u7edf&#xff1a;<\/p>\n<p>class MultiTrajectoryAnalysisDemo:<br \/>\n    &#034;&#034;&#034;\u591a\u76ee\u6807\u8f68\u8ff9\u5206\u6790\u6f14\u793a&#034;&#034;&#034;<\/p>\n<p>    def __init__(self):<br \/>\n        self.trajectories &#061; []<br \/>\n        self.visualizer &#061; EnhancedTrajectoryVisualizer()<br \/>\n        self.plotter &#061; None<br \/>\n        self.color_palette &#061; [<br \/>\n            [1, 0, 0],    # \u7ea2\u8272<br \/>\n            [0, 1, 0],    # \u7eff\u8272<br \/>\n            [0, 0, 1],    # \u84dd\u8272<br \/>\n            [1, 1, 0],    # \u9ec4\u8272<br \/>\n            [1, 0, 1],    # \u7d2b\u8272<br \/>\n            [0, 1, 1],    # \u9752\u8272<br \/>\n        ]<\/p>\n<p>    def generate_data(self, num_trajectories&#061;4):<br \/>\n        &#034;&#034;&#034;\u751f\u6210\u591a\u76ee\u6807\u8f68\u8ff9\u6570\u636e&#034;&#034;&#034;<br \/>\n        print(f&#034;\u751f\u6210 {num_trajectories} \u4e2a\u76ee\u6807\u7684\u8f68\u8ff9\u6570\u636e&#8230;&#034;)<\/p>\n<p>        self.trajectories &#061; generate_multiple_trajectories(num_trajectories, 50)<\/p>\n<p>        # \u6253\u5370\u7edf\u8ba1\u4fe1\u606f<br \/>\n        for i, traj in enumerate(self.trajectories):<br \/>\n            stats &#061; traj.get_statistics()<br \/>\n            print(f&#034;\u8f68\u8ff9 {traj.id}: {stats[&#039;point_count&#039;]}\u4e2a\u70b9, &#034;<br \/>\n                  f&#034;\u8ddd\u79bb{stats[&#039;total_distance&#039;]:.0f}m, &#034;<br \/>\n                  f&#034;\u5e73\u5747\u901f\u5ea6{stats[&#039;avg_speed&#039;]:.1f}m\/s&#034;)<\/p>\n<p>    def create_comparison_plot(self, plotter):<br \/>\n        &#034;&#034;&#034;\u521b\u5efa\u5bf9\u6bd4\u5206\u6790\u56fe\u8868&#034;&#034;&#034;<br \/>\n        if not self.trajectories:<br \/>\n            return<\/p>\n<p>        # \u63d0\u53d6\u7edf\u8ba1\u6570\u636e<br \/>\n        traj_ids &#061; []<br \/>\n        avg_speeds &#061; []<br \/>\n        total_distances &#061; []<br \/>\n        durations &#061; []<\/p>\n<p>        for traj in self.trajectories:<br \/>\n            stats &#061; traj.get_statistics()<br \/>\n            traj_ids.append(traj.id)<br \/>\n            avg_speeds.append(stats[&#039;avg_speed&#039;])<br \/>\n            total_distances.append(stats[&#039;total_distance&#039;])<br \/>\n            durations.append(stats[&#039;duration&#039;])<\/p>\n<p>        # \u521b\u5efa\u5b50\u56fe<br \/>\n        plotter.subplot(0, 1)<\/p>\n<p>        # \u5e73\u5747\u901f\u5ea6\u6761\u5f62\u56fe<br \/>\n        y_pos &#061; np.arange(len(traj_ids))<\/p>\n<p>        # \u521b\u5efa\u6761\u5f62\u56fe\u7f51\u683c<br \/>\n        bar_width &#061; 0.6<br \/>\n        bars &#061; []<\/p>\n<p>        for i, (traj_id, speed) in enumerate(zip(traj_ids, avg_speeds)):<br \/>\n            bar &#061; pv.Box(bounds&#061;[<br \/>\n                i &#8211; bar_width\/2, i &#043; bar_width\/2,<br \/>\n                0, speed\/50,  # \u7f29\u653e\u901f\u5ea6\u503c<br \/>\n                0, 1<br \/>\n            ])<br \/>\n            bars.append(bar)<\/p>\n<p>        # \u5408\u5e76\u6761\u5f62<br \/>\n        if bars:<br \/>\n            bar_mesh &#061; bars[0]<br \/>\n            for bar in bars[1:]:<br \/>\n                bar_mesh &#061; bar_mesh.merge(bar)<\/p>\n<p>            # \u4e3a\u6bcf\u4e2a\u6761\u5f62\u8bbe\u7f6e\u989c\u8272<br \/>\n            colors &#061; []<br \/>\n            for i, traj_id in enumerate(traj_ids):<br \/>\n                color_idx &#061; i % len(self.color_palette)<br \/>\n                colors.extend([self.color_palette[color_idx]] * bar_mesh.n_points)<\/p>\n<p>            bar_mesh[&#039;colors&#039;] &#061; colors<\/p>\n<p>            plotter.add_mesh(<br \/>\n                bar_mesh,<br \/>\n                scalars&#061;&#039;colors&#039;,<br \/>\n                rgb&#061;True,<br \/>\n                name&#061;&#039;speed_bars&#039;,<br \/>\n                show_edges&#061;True<br \/>\n            )<\/p>\n<p>        # \u8bbe\u7f6e\u5750\u6807\u8f74<br \/>\n        plotter.add_text(<br \/>\n            &#034;\u5e73\u5747\u901f\u5ea6 (m\/s)&#034;,<br \/>\n            position&#061;&#039;upper_center&#039;,<br \/>\n            font_size&#061;10,<br \/>\n            name&#061;&#039;speed_title&#039;<br \/>\n        )<\/p>\n<p>        # \u6dfb\u52a0\u8f68\u8ff9ID\u6807\u7b7e<br \/>\n        for i, traj_id in enumerate(traj_ids):<br \/>\n            plotter.add_text(<br \/>\n                traj_id,<br \/>\n                position&#061;(i, -0.5, 0),<br \/>\n                font_size&#061;8,<br \/>\n                name&#061;f&#039;label_{traj_id}&#039;<br \/>\n            )<\/p>\n<p>        plotter.subplot(0, 0)  # \u8fd4\u56de\u4e3b\u56fe<\/p>\n<p>    def add_legend(self, plotter):<br \/>\n        &#034;&#034;&#034;\u6dfb\u52a0\u56fe\u4f8b&#034;&#034;&#034;<br \/>\n        if not self.trajectories:<br \/>\n            return<\/p>\n<p>        legend_text &#061; &#034;\u8f68\u8ff9\u56fe\u4f8b:\\\\n&#034;<\/p>\n<p>        for i, traj in enumerate(self.trajectories):<br \/>\n            color_idx &#061; i % len(self.color_palette)<br \/>\n            color &#061; self.color_palette[color_idx]<br \/>\n            color_hex &#061; &#039;#{:02x}{:02x}{:02x}&#039;.format(<br \/>\n                int(color[0]*255), int(color[1]*255), int(color[2]*255)<br \/>\n            )<\/p>\n<p>            stats &#061; traj.get_statistics()<br \/>\n            legend_text &#043;&#061; f&#034;&lt;span style&#061;&#039;color:{color_hex}&#039;&gt;\u25a0&lt;\/span&gt; &#034;<br \/>\n            legend_text &#043;&#061; f&#034;{traj.id}: {stats[&#039;avg_speed&#039;]:.1f}m\/s, &#034;<br \/>\n            legend_text &#043;&#061; f&#034;{stats[&#039;total_distance&#039;]:.0f}m\\\\n&#034;<\/p>\n<p>        plotter.add_text(<br \/>\n            legend_text,<br \/>\n            position&#061;&#039;upper_right&#039;,<br \/>\n            font_size&#061;9,<br \/>\n            name&#061;&#039;legend&#039;,<br \/>\n            font&#061;&#039;arial&#039;<br \/>\n        )<\/p>\n<p>    def add_trajectory_controls(self, plotter):<br \/>\n        &#034;&#034;&#034;\u6dfb\u52a0\u8f68\u8ff9\u63a7\u5236&#034;&#034;&#034;<br \/>\n        if not self.trajectories:<br \/>\n            return<\/p>\n<p>        def toggle_trajectory(traj_index, visible):<br \/>\n            &#034;&#034;&#034;\u5207\u6362\u8f68\u8ff9\u663e\u793a&#034;&#034;&#034;<br \/>\n            if 0 &lt;&#061; traj_index &lt; len(self.trajectories):<br \/>\n                traj &#061; self.trajectories[traj_index]<br \/>\n                actor_name &#061; f&#039;trajectory_{traj.id}&#039;<\/p>\n<p>                if visible:<br \/>\n                    # \u663e\u793a\u8f68\u8ff9<br \/>\n                    if actor_name in plotter.actors:<br \/>\n                        plotter.remove_actor(plotter.actors[actor_name])<\/p>\n<p>                    # \u521b\u5efa\u8f68\u8ff9<br \/>\n                    mesh &#061; self.visualizer.create_colored_trajectory(<br \/>\n                        traj,<br \/>\n                        color_by&#061;&#039;speed&#039;,<br \/>\n                        colormap&#061;&#039;plasma&#039;,<br \/>\n                        style&#061;&#039;tube&#039;,<br \/>\n                        radius&#061;30<br \/>\n                    )<\/p>\n<p>                    if mesh:<br \/>\n                        plotter.add_mesh(<br \/>\n                            mesh,<br \/>\n                            scalars&#061;&#039;speed&#039;,<br \/>\n                            cmap&#061;&#039;plasma&#039;,<br \/>\n                            opacity&#061;0.7,<br \/>\n                            name&#061;actor_name,<br \/>\n                            show_edges&#061;False<br \/>\n                        )<br \/>\n                else:<br \/>\n                    # \u9690\u85cf\u8f68\u8ff9<br \/>\n                    if actor_name in plotter.actors:<br \/>\n                        plotter.remove_actor(plotter.actors[actor_name])<\/p>\n<p>        # \u4e3a\u6bcf\u4e2a\u8f68\u8ff9\u6dfb\u52a0\u590d\u9009\u6846<br \/>\n        for i, traj in enumerate(self.trajectories):<br \/>\n            plotter.add_checkbox_button_widget(<br \/>\n                lambda state, idx&#061;i: toggle_trajectory(idx, state),<br \/>\n                value&#061;True,<br \/>\n                position&#061;(10, 10 &#043; i*30),<br \/>\n                size&#061;20,<br \/>\n                border_size&#061;2,<br \/>\n                color_on&#061;&#039;green&#039;,<br \/>\n                color_off&#061;&#039;red&#039;,<br \/>\n                background_color&#061;&#039;white&#039;<br \/>\n            )<\/p>\n<p>            # \u6dfb\u52a0\u6807\u7b7e<br \/>\n            plotter.add_text(<br \/>\n                traj.id,<br \/>\n                position&#061;(40, 10 &#043; i*30),<br \/>\n                font_size&#061;10,<br \/>\n                color&#061;&#039;white&#039;,<br \/>\n                name&#061;f&#039;checkbox_label_{i}&#039;<br \/>\n            )<\/p>\n<p>    def add_time_synchronization(self, plotter):<br \/>\n        &#034;&#034;&#034;\u6dfb\u52a0\u65f6\u95f4\u540c\u6b65&#034;&#034;&#034;<br \/>\n        if not self.trajectories:<br \/>\n            return<\/p>\n<p>        # \u627e\u5230\u5171\u540c\u7684\u65f6\u95f4\u8303\u56f4<br \/>\n        all_times &#061; []<br \/>\n        for traj in self.trajectories:<br \/>\n            if traj.points:<br \/>\n                times &#061; [p.timestamp for p in traj.points]<br \/>\n                if isinstance(times[0], datetime):<br \/>\n                    times &#061; [t.timestamp() for t in times]<br \/>\n                all_times.extend(times)<\/p>\n<p>        if not all_times:<br \/>\n            return<\/p>\n<p>        time_min &#061; min(all_times)<br \/>\n        time_max &#061; max(all_times)<\/p>\n<p>        # \u6dfb\u52a0\u65f6\u95f4\u6ed1\u5757<br \/>\n        def update_all_trajectories(time_value):<br \/>\n            &#034;&#034;&#034;\u66f4\u65b0\u6240\u6709\u8f68\u8ff9\u7684\u65f6\u95f4\u70b9&#034;&#034;&#034;<br \/>\n            for traj in self.trajectories:<br \/>\n                # \u627e\u5230\u5bf9\u5e94\u65f6\u95f4\u7684\u70b9<br \/>\n                point &#061; traj.get_point_at_time(time_value)<br \/>\n                if point:<br \/>\n                    # \u66f4\u65b0\u6807\u8bb0\u70b9<br \/>\n                    marker_name &#061; f&#039;marker_{traj.id}&#039;<br \/>\n                    if marker_name in plotter.actors:<br \/>\n                        plotter.remove_actor(plotter.actors[marker_name])<\/p>\n<p>                    # \u521b\u5efa\u6807\u8bb0<br \/>\n                    marker &#061; pv.Sphere(center&#061;point.position, radius&#061;50)<br \/>\n                    color_idx &#061; self.trajectories.index(traj) % len(self.color_palette)<br \/>\n                    color &#061; self.color_palette[color_idx]<\/p>\n<p>                    plotter.add_mesh(<br \/>\n                        marker,<br \/>\n                        color&#061;color,<br \/>\n                        name&#061;marker_name<br \/>\n                    )<\/p>\n<p>        plotter.add_slider_widget(<br \/>\n            update_all_trajectories,<br \/>\n            [time_min, time_max],<br \/>\n            value&#061;time_min,<br \/>\n            title&#061;&#039;\u540c\u6b65\u65f6\u95f4&#039;,<br \/>\n            pointa&#061;(0.7, 0.1),<br \/>\n            pointb&#061;(0.9, 0.1),<br \/>\n            style&#061;&#039;modern&#039;<br \/>\n        )<\/p>\n<p>    def run_demo(self, num_trajectories&#061;4):<br \/>\n        &#034;&#034;&#034;\u8fd0\u884c\u6f14\u793a&#034;&#034;&#034;<br \/>\n        print(&#034;\u591a\u76ee\u6807\u8f68\u8ff9\u5bf9\u6bd4\u5206\u6790\u6f14\u793a&#034;)<br \/>\n        print(&#034;&#061;&#034; * 50)<\/p>\n<p>        # \u751f\u6210\u6570\u636e<br \/>\n        self.generate_data(num_trajectories)<\/p>\n<p>        # \u521b\u5efa\u7ed8\u56fe\u7a97\u53e3<br \/>\n        self.plotter &#061; pv.Plotter(window_size&#061;(1800, 900),<br \/>\n                                 shape&#061;(1, 2),<br \/>\n                                 title&#061;&#034;\u591a\u76ee\u6807\u8f68\u8ff9\u5bf9\u6bd4\u5206\u6790&#034;)<\/p>\n<p>        # \u4e3b\u56fe (3D\u8f68\u8ff9)<br \/>\n        self.plotter.subplot(0, 0)<\/p>\n<p>        # \u6dfb\u52a0\u5730\u5f62<br \/>\n        x &#061; np.linspace(-10000, 10000, 50)<br \/>\n        y &#061; np.linspace(-10000, 10000, 50)<br \/>\n        xx, yy &#061; np.meshgrid(x, y)<br \/>\n        z &#061; 1000 &#043; 300 * np.sin(0.0005 * xx) * np.cos(0.0005 * yy)<br \/>\n        terrain &#061; pv.StructuredGrid(xx, yy, z)<br \/>\n        terrain[&#039;elevation&#039;] &#061; z.ravel()<\/p>\n<p>        self.plotter.add_mesh(<br \/>\n            terrain,<br \/>\n            cmap&#061;&#039;terrain&#039;,<br \/>\n            scalars&#061;&#039;elevation&#039;,<br \/>\n            opacity&#061;0.5,<br \/>\n            show_edges&#061;False,<br \/>\n            name&#061;&#039;terrain&#039;<br \/>\n        )<\/p>\n<p>        # \u6dfb\u52a0\u8f68\u8ff9<br \/>\n        for i, traj in enumerate(self.trajectories):<br \/>\n            color_idx &#061; i % len(self.color_palette)<br \/>\n            color &#061; self.color_palette[color_idx]<\/p>\n<p>            # \u521b\u5efa\u8f68\u8ff9<br \/>\n            mesh &#061; self.visualizer.create_colored_trajectory(<br \/>\n                traj,<br \/>\n                color_by&#061;&#039;speed&#039;,<br \/>\n                colormap&#061;&#039;plasma&#039;,<br \/>\n                style&#061;&#039;tube&#039;,<br \/>\n                radius&#061;30<br \/>\n            )<\/p>\n<p>            if mesh:<br \/>\n                self.plotter.add_mesh(<br \/>\n                    mesh,<br \/>\n                    scalars&#061;&#039;speed&#039;,<br \/>\n                    cmap&#061;&#039;plasma&#039;,<br \/>\n                    opacity&#061;0.7,<br \/>\n                    name&#061;f&#039;trajectory_{traj.id}&#039;,<br \/>\n                    show_edges&#061;False<br \/>\n                )<\/p>\n<p>            # \u6dfb\u52a0\u8d77\u59cb\u70b9\u548c\u7ec8\u70b9<br \/>\n            if traj.points:<br \/>\n                # \u8d77\u59cb\u70b9<br \/>\n                start_mesh &#061; pv.Sphere(center&#061;traj.points[0].position, radius&#061;100)<br \/>\n                self.plotter.add_mesh(<br \/>\n                    start_mesh,<br \/>\n                    color&#061;&#039;green&#039;,<br \/>\n                    name&#061;f&#039;start_{traj.id}&#039;<br \/>\n                )<\/p>\n<p>                # \u7ec8\u70b9<br \/>\n                end_mesh &#061; pv.Sphere(center&#061;traj.points[-1].position, radius&#061;100)<br \/>\n                self.plotter.add_mesh(<br \/>\n                    end_mesh,<br \/>\n                    color&#061;&#039;red&#039;,<br \/>\n                    name&#061;f&#039;end_{traj.id}&#039;<br \/>\n                )<\/p>\n<p>        # \u8bbe\u7f6e\u4e3b\u56fe<br \/>\n        self.plotter.add_axes()<br \/>\n        self.plotter.show_grid()<br \/>\n        self.plotter.camera_position &#061; [(20000, 20000, 10000), (0, 0, 5000), (0, 0, 1)]<br \/>\n        self.plotter.set_background(&#039;linear_gradient&#039;, bottom&#061;&#039;#0a0a2a&#039;, top&#061;&#039;#1a1a3a&#039;)<\/p>\n<p>        # \u6dfb\u52a0\u56fe\u4f8b<br \/>\n        self.add_legend(self.plotter)<\/p>\n<p>        # \u6dfb\u52a0\u8f68\u8ff9\u63a7\u5236<br \/>\n        self.add_trajectory_controls(self.plotter)<\/p>\n<p>        # \u6dfb\u52a0\u65f6\u95f4\u540c\u6b65<br \/>\n        self.add_time_synchronization(self.plotter)<\/p>\n<p>        # \u5bf9\u6bd4\u5206\u6790\u56fe\u8868<br \/>\n        self.create_comparison_plot(self.plotter)<\/p>\n<p>        # \u6dfb\u52a0\u63a7\u5236\u8bf4\u660e<br \/>\n        controls &#061; &#034;\u63a7\u5236\u8bf4\u660e:\\\\n&#034;<br \/>\n        controls &#043;&#061; &#034;\u5de6\u4fa7\u590d\u9009\u6846: \u663e\u793a\/\u9690\u85cf\u8f68\u8ff9\\\\n&#034;<br \/>\n        controls &#043;&#061; &#034;\u65f6\u95f4\u6ed1\u5757: \u540c\u6b65\u65f6\u95f4\u70b9\\\\n&#034;<br \/>\n        controls &#043;&#061; &#034;\u9f20\u6807\u4ea4\u4e92: \u65cb\u8f6c\/\u5e73\u79fb\/\u7f29\u653e\\\\n&#034;<\/p>\n<p>        self.plotter.add_text(<br \/>\n            controls,<br \/>\n            position&#061;&#039;lower_left&#039;,<br \/>\n            font_size&#061;9,<br \/>\n            color&#061;&#039;cyan&#039;,<br \/>\n            name&#061;&#039;controls&#039;<br \/>\n        )<\/p>\n<p>        print(&#034;\\\\n\u6f14\u793a\u5df2\u542f\u52a8&#034;)<br \/>\n        print(&#034;\u4f7f\u7528\u5de6\u4fa7\u590d\u9009\u6846\u63a7\u5236\u8f68\u8ff9\u663e\u793a&#034;)<br \/>\n        print(&#034;\u4f7f\u7528\u65f6\u95f4\u6ed1\u5757\u540c\u6b65\u67e5\u770b\u4e0d\u540c\u65f6\u95f4\u70b9\u7684\u4f4d\u7f6e&#034;)<\/p>\n<p>        # \u663e\u793a<br \/>\n        self.plotter.show()<\/p>\n<p># \u8fd0\u884c\u6848\u4f8b2<br \/>\ndef run_case2():<br \/>\n    demo &#061; MultiTrajectoryAnalysisDemo()<br \/>\n    demo.run_demo(num_trajectories&#061;4)<\/p>\n<p>if __name__ &#061;&#061; &#034;__main__&#034;:<br \/>\n    run_case2()<\/p>\n<h3>6. \u6848\u4f8b3&#xff1a;\u96f7\u8fbe\u63a2\u6d4b\u5386\u53f2\u53ef\u89c6\u5316<\/h3>\n<h4>6.1 \u96f7\u8fbe\u63a2\u6d4b\u6570\u636e\u751f\u6210<\/h4>\n<p>\u6a21\u62df\u96f7\u8fbe\u63a2\u6d4b\u5386\u53f2\u6570\u636e&#xff1a;<\/p>\n<p>def generate_radar_detection_history(num_targets&#061;3, detection_points_per_target&#061;20):<br \/>\n    &#034;&#034;&#034;\u751f\u6210\u96f7\u8fbe\u63a2\u6d4b\u5386\u53f2\u6570\u636e&#034;&#034;&#034;<br \/>\n    from datetime import datetime, timedelta<br \/>\n    import random<\/p>\n<p>    detections &#061; []<br \/>\n    start_time &#061; datetime(2024, 1, 1, 12, 0, 0)<\/p>\n<p>    # \u96f7\u8fbe\u4f4d\u7f6e<br \/>\n    radar_position &#061; np.array([0, 0, 0])<\/p>\n<p>    for target_idx in range(num_targets):<br \/>\n        target_id &#061; f&#034;Target{target_idx&#043;1:03d}&#034;<\/p>\n<p>        # \u76ee\u6807\u8d77\u59cb\u4f4d\u7f6e<br \/>\n        if target_idx &#061;&#061; 0:<br \/>\n            start_pos &#061; np.array([-5000, 0, 2000])<br \/>\n        elif target_idx &#061;&#061; 1:<br \/>\n            start_pos &#061; np.array([0, 5000, 2500])<br \/>\n        else:<br \/>\n            start_pos &#061; np.array([5000, 0, 3000])<\/p>\n<p>        # \u76ee\u6807\u901f\u5ea6<br \/>\n        speed &#061; 200 &#043; random.uniform(-50, 50)<\/p>\n<p>        for i in range(detection_points_per_target):<br \/>\n            t &#061; i * 30  # 30\u79d2\u95f4\u9694<\/p>\n<p>            # \u8ba1\u7b97\u76ee\u6807\u4f4d\u7f6e<br \/>\n            if target_idx &#061;&#061; 0:<br \/>\n                # \u76f4\u7ebf\u8fd0\u52a8<br \/>\n                x &#061; start_pos[0] &#043; t * 20<br \/>\n                y &#061; start_pos[1] &#043; t * 10<br \/>\n                z &#061; start_pos[2] &#043; t * 2<br \/>\n            elif target_idx &#061;&#061; 1:<br \/>\n                # \u5706\u5468\u8fd0\u52a8<br \/>\n                angle &#061; math.radians(t * 2)<br \/>\n                radius &#061; 4000<br \/>\n                x &#061; radius * math.cos(angle)<br \/>\n                y &#061; radius * math.sin(angle)<br \/>\n                z &#061; start_pos[2] &#043; math.sin(angle) * 200<br \/>\n            else:<br \/>\n                # \u968f\u673a\u8fd0\u52a8<br \/>\n                x &#061; start_pos[0] &#043; random.uniform(-100, 100) * t<br \/>\n                y &#061; start_pos[1] &#043; random.uniform(-100, 100) * t<br \/>\n                z &#061; start_pos[2] &#043; random.uniform(-5, 5) * t<\/p>\n<p>            target_position &#061; np.array([x, y, z])<\/p>\n<p>            # \u8ba1\u7b97\u96f7\u8fbe\u63a2\u6d4b\u53c2\u6570<br \/>\n            range_vec &#061; target_position &#8211; radar_position<br \/>\n            distance &#061; np.linalg.norm(range_vec)<\/p>\n<p>            # \u8ba1\u7b97\u89d2\u5ea6<br \/>\n            azimuth &#061; math.degrees(math.atan2(range_vec[1], range_vec[0]))<br \/>\n            elevation &#061; math.degrees(math.asin(range_vec[2] \/ distance))<\/p>\n<p>            # \u6dfb\u52a0\u566a\u58f0<br \/>\n            range_noise &#061; random.gauss(0, 10)  # \u8ddd\u79bb\u566a\u58f0<br \/>\n            angle_noise &#061; random.gauss(0, 0.5)  # \u89d2\u5ea6\u566a\u58f0<\/p>\n<p>            # \u63a2\u6d4b\u4fe1\u566a\u6bd4<br \/>\n            snr &#061; 20 &#8211; distance\/1000 &#043; random.uniform(-5, 5)<\/p>\n<p>            # \u521b\u5efa\u63a2\u6d4b\u70b9<br \/>\n            timestamp &#061; start_time &#043; timedelta(seconds&#061;t)<br \/>\n            detection &#061; {<br \/>\n                &#039;timestamp&#039;: timestamp,<br \/>\n                &#039;target_id&#039;: target_id,<br \/>\n                &#039;position&#039;: target_position.tolist(),<br \/>\n                &#039;distance&#039;: distance &#043; range_noise,<br \/>\n                &#039;azimuth&#039;: azimuth &#043; angle_noise,<br \/>\n                &#039;elevation&#039;: elevation &#043; angle_noise,<br \/>\n                &#039;snr&#039;: snr,<br \/>\n                &#039;radar_position&#039;: radar_position.tolist(),<br \/>\n                &#039;is_tracked&#039;: random.random() &gt; 0.3  # 70%\u7684\u6982\u7387\u88ab\u8ddf\u8e2a<br \/>\n            }<\/p>\n<p>            detections.append(detection)<\/p>\n<p>    return detections, radar_position<\/p>\n<p>def save_detections_to_csv(detections, filename&#061;&#039;radar_detections.csv&#039;):<br \/>\n    &#034;&#034;&#034;\u4fdd\u5b58\u63a2\u6d4b\u6570\u636e\u5230CSV&#034;&#034;&#034;<br \/>\n    data &#061; []<\/p>\n<p>    for det in detections:<br \/>\n        row &#061; {<br \/>\n            &#039;timestamp&#039;: det[&#039;timestamp&#039;],<br \/>\n            &#039;target_id&#039;: det[&#039;target_id&#039;],<br \/>\n            &#039;x&#039;: det[&#039;position&#039;][0],<br \/>\n            &#039;y&#039;: det[&#039;position&#039;][1],<br \/>\n            &#039;z&#039;: det[&#039;position&#039;][2],<br \/>\n            &#039;distance&#039;: det[&#039;distance&#039;],<br \/>\n            &#039;azimuth&#039;: det[&#039;azimuth&#039;],<br \/>\n            &#039;elevation&#039;: det[&#039;elevation&#039;],<br \/>\n            &#039;snr&#039;: det[&#039;snr&#039;],<br \/>\n            &#039;is_tracked&#039;: det[&#039;is_tracked&#039;]<br \/>\n        }<br \/>\n        data.append(row)<\/p>\n<p>    df &#061; pd.DataFrame(data)<br \/>\n    df.to_csv(filename, index&#061;False)<br \/>\n    print(f&#034;\u63a2\u6d4b\u6570\u636e\u5df2\u4fdd\u5b58\u5230: {filename}&#034;)<\/p>\n<p>    return df<\/p>\n<h4>6.2 \u5b8c\u6574\u7684\u6848\u4f8b3\u5b9e\u73b0<\/h4>\n<p>\u5b9e\u73b0\u96f7\u8fbe\u63a2\u6d4b\u5386\u53f2\u7684\u4e09\u7ef4\u53ef\u89c6\u5316&#xff1a;<\/p>\n<p>class RadarDetectionVisualizer:<br \/>\n    &#034;&#034;&#034;\u96f7\u8fbe\u63a2\u6d4b\u53ef\u89c6\u5316\u5668&#034;&#034;&#034;<\/p>\n<p>    def __init__(self):<br \/>\n        self.detections &#061; []<br \/>\n        self.radar_position &#061; np.array([0, 0, 0])<br \/>\n        self.plotter &#061; None<\/p>\n<p>    def load_detection_data(self, num_targets&#061;3):<br \/>\n        &#034;&#034;&#034;\u52a0\u8f7d\u6216\u751f\u6210\u63a2\u6d4b\u6570\u636e&#034;&#034;&#034;<br \/>\n        try:<br \/>\n            # \u5c1d\u8bd5\u4ece\u6587\u4ef6\u52a0\u8f7d<br \/>\n            df &#061; pd.read_csv(&#039;radar_detections.csv&#039;)<\/p>\n<p>            self.detections &#061; []<br \/>\n            for _, row in df.iterrows():<br \/>\n                detection &#061; {<br \/>\n                    &#039;timestamp&#039;: pd.to_datetime(row[&#039;timestamp&#039;]),<br \/>\n                    &#039;target_id&#039;: row[&#039;target_id&#039;],<br \/>\n                    &#039;position&#039;: np.array([row[&#039;x&#039;], row[&#039;y&#039;], row[&#039;z&#039;]]),<br \/>\n                    &#039;distance&#039;: row[&#039;distance&#039;],<br \/>\n                    &#039;azimuth&#039;: row[&#039;azimuth&#039;],<br \/>\n                    &#039;elevation&#039;: row[&#039;elevation&#039;],<br \/>\n                    &#039;snr&#039;: row[&#039;snr&#039;],<br \/>\n                    &#039;is_tracked&#039;: bool(row[&#039;is_tracked&#039;])<br \/>\n                }<br \/>\n                self.detections.append(detection)<\/p>\n<p>            print(f&#034;\u4ece\u6587\u4ef6\u52a0\u8f7d {len(self.detections)} \u4e2a\u63a2\u6d4b\u70b9&#034;)<\/p>\n<p>        except FileNotFoundError:<br \/>\n            print(&#034;\u672a\u627e\u5230\u63a2\u6d4b\u6570\u636e\u6587\u4ef6&#xff0c;\u751f\u6210\u6a21\u62df\u6570\u636e&#8230;&#034;)<br \/>\n            self.detections, self.radar_position &#061; generate_radar_detection_history(<br \/>\n                num_targets, 20<br \/>\n            )<br \/>\n            save_detections_to_csv(self.detections)<\/p>\n<p>        return len(self.detections)<\/p>\n<p>    def group_detections_by_target(self):<br \/>\n        &#034;&#034;&#034;\u6309\u76ee\u6807\u5206\u7ec4\u63a2\u6d4b\u70b9&#034;&#034;&#034;<br \/>\n        targets &#061; {}<\/p>\n<p>        for det in self.detections:<br \/>\n            target_id &#061; det[&#039;target_id&#039;]<br \/>\n            if target_id not in targets:<br \/>\n                targets[target_id] &#061; []<br \/>\n            targets[target_id].append(det)<\/p>\n<p>        return targets<\/p>\n<p>    def create_radar_model(self, plotter, radar_position, scale&#061;100):<br \/>\n        &#034;&#034;&#034;\u521b\u5efa\u96f7\u8fbe\u6a21\u578b&#034;&#034;&#034;<br \/>\n        # \u96f7\u8fbe\u57fa\u5ea7<br \/>\n        base &#061; pv.Cylinder(center&#061;radar_position, direction&#061;[0, 0, 1],<br \/>\n                          radius&#061;scale, height&#061;scale*0.5)<\/p>\n<p>        # \u96f7\u8fbe\u5929\u7ebf<br \/>\n        antenna &#061; pv.Cone(center&#061;radar_position &#043; [0, 0, scale*0.75],<br \/>\n                         direction&#061;[0, 0, 1], height&#061;scale, radius&#061;scale*0.3)<\/p>\n<p>        # \u6dfb\u52a0\u96f7\u8fbe\u6a21\u578b<br \/>\n        plotter.add_mesh(base, color&#061;&#039;gray&#039;, name&#061;&#039;radar_base&#039;)<br \/>\n        plotter.add_mesh(antenna, color&#061;&#039;darkgray&#039;, name&#061;&#039;radar_antenna&#039;)<\/p>\n<p>        return base, antenna<\/p>\n<p>    def create_detection_points(self, detections, plotter, color_by&#061;&#039;snr&#039;):<br \/>\n        &#034;&#034;&#034;\u521b\u5efa\u63a2\u6d4b\u70b9\u53ef\u89c6\u5316&#034;&#034;&#034;<br \/>\n        if not detections:<br \/>\n            return None<\/p>\n<p>        # \u63d0\u53d6\u4f4d\u7f6e\u548c\u5176\u4ed6\u5c5e\u6027<br \/>\n        positions &#061; []<br \/>\n        snr_values &#061; []<br \/>\n        distances &#061; []<br \/>\n        target_ids &#061; []<br \/>\n        timestamps &#061; []<\/p>\n<p>        for det in detections:<br \/>\n            positions.append(det[&#039;position&#039;])<br \/>\n            snr_values.append(det[&#039;snr&#039;])<br \/>\n            distances.append(det[&#039;distance&#039;])<br \/>\n            target_ids.append(det[&#039;target_id&#039;])<br \/>\n            timestamps.append(det[&#039;timestamp&#039;])<\/p>\n<p>        positions &#061; np.array(positions)<\/p>\n<p>        # \u521b\u5efa\u70b9\u4e91<br \/>\n        points_mesh &#061; pv.PolyData(positions)<\/p>\n<p>        # \u6dfb\u52a0\u5c5e\u6027<br \/>\n        points_mesh[&#039;snr&#039;] &#061; snr_values<br \/>\n        points_mesh[&#039;distance&#039;] &#061; distances<br \/>\n        points_mesh[&#039;target_id&#039;] &#061; target_ids<\/p>\n<p>        # \u65f6\u95f4\u5c5e\u6027<br \/>\n        if isinstance(timestamps[0], datetime):<br \/>\n            timestamps_sec &#061; [t.timestamp() for t in timestamps]<br \/>\n        else:<br \/>\n            timestamps_sec &#061; timestamps<\/p>\n<p>        time_min &#061; min(timestamps_sec)<br \/>\n        time_max &#061; max(timestamps_sec)<br \/>\n        time_norm &#061; [(t &#8211; time_min) \/ (time_max &#8211; time_min &#043; 1e-10) for t in timestamps_sec]<br \/>\n        points_mesh[&#039;time&#039;] &#061; time_norm<\/p>\n<p>        return points_mesh<\/p>\n<p>    def create_detection_lines(self, detections_by_target, plotter):<br \/>\n        &#034;&#034;&#034;\u521b\u5efa\u63a2\u6d4b\u8fde\u7ebf&#034;&#034;&#034;<br \/>\n        for target_id, detections in detections_by_target.items():<br \/>\n            if len(detections) &lt; 2:<br \/>\n                continue<\/p>\n<p>            # \u6309\u65f6\u95f4\u6392\u5e8f<br \/>\n            detections_sorted &#061; sorted(detections, key&#061;lambda x: x[&#039;timestamp&#039;])<\/p>\n<p>            # \u63d0\u53d6\u4f4d\u7f6e<br \/>\n            positions &#061; [d[&#039;position&#039;] for d in detections_sorted]<br \/>\n            positions &#061; np.array(positions)<\/p>\n<p>            # \u521b\u5efa\u7ebf<br \/>\n            line &#061; pv.lines_from_points(positions)<\/p>\n<p>            # \u6dfb\u52a0\u65f6\u95f4\u5c5e\u6027<br \/>\n            timestamps &#061; [d[&#039;timestamp&#039;] for d in detections_sorted]<br \/>\n            if isinstance(timestamps[0], datetime):<br \/>\n                timestamps_sec &#061; [t.timestamp() for t in timestamps]<br \/>\n            else:<br \/>\n                timestamps_sec &#061; timestamps<\/p>\n<p>            time_min &#061; min(timestamps_sec)<br \/>\n            time_max &#061; max(timestamps_sec)<br \/>\n            time_norm &#061; [(t &#8211; time_min) \/ (time_max &#8211; time_min &#043; 1e-10) for t in timestamps_sec]<\/p>\n<p>            # \u7531\u4e8elines_from_points\u4f1a\u63d2\u503c&#xff0c;\u6211\u4eec\u9700\u8981\u5c06\u65f6\u95f4\u5c5e\u6027\u6620\u5c04\u5230\u7ebf\u4e0a<br \/>\n            # \u4f7f\u7528\u53c2\u6570\u5316\u65b9\u6cd5<br \/>\n            line_length &#061; line.length<br \/>\n            line[&#039;time&#039;] &#061; np.linspace(0, 1, line.n_points)<\/p>\n<p>            # \u6839\u636e\u76ee\u6807ID\u9009\u62e9\u989c\u8272<br \/>\n            target_colors &#061; {<br \/>\n                &#039;Target001&#039;: [1, 0, 0],  # \u7ea2\u8272<br \/>\n                &#039;Target002&#039;: [0, 1, 0],  # \u7eff\u8272<br \/>\n                &#039;Target003&#039;: [0, 0, 1],  # \u84dd\u8272<br \/>\n                &#039;Target004&#039;: [1, 1, 0],  # \u9ec4\u8272<br \/>\n            }<\/p>\n<p>            color &#061; target_colors.get(target_id, [0.5, 0.5, 0.5])<\/p>\n<p>            # \u6dfb\u52a0\u8fde\u7ebf<br \/>\n            plotter.add_mesh(<br \/>\n                line,<br \/>\n                color&#061;color,<br \/>\n                line_width&#061;3,<br \/>\n                opacity&#061;0.6,<br \/>\n                name&#061;f&#039;detection_line_{target_id}&#039;<br \/>\n            )<\/p>\n<p>    def create_detection_cones(self, detections, plotter, radar_position, max_range&#061;10000):<br \/>\n        &#034;&#034;&#034;\u521b\u5efa\u63a2\u6d4b\u9525\u4f53&#xff08;\u8868\u793a\u96f7\u8fbe\u6ce2\u675f&#xff09;&#034;&#034;&#034;<br \/>\n        if not detections:<br \/>\n            return<\/p>\n<p>        # \u6309\u65f6\u95f4\u5206\u7ec4\u63a2\u6d4b\u70b9<br \/>\n        time_groups &#061; {}<br \/>\n        for det in detections:<br \/>\n            # \u7b80\u5316\u65f6\u95f4\u7cbe\u5ea6&#xff08;\u630910\u79d2\u5206\u7ec4&#xff09;<br \/>\n            if isinstance(det[&#039;timestamp&#039;], datetime):<br \/>\n                time_key &#061; det[&#039;timestamp&#039;].replace(second&#061;det[&#039;timestamp&#039;].second\/\/10 * 10)<br \/>\n            else:<br \/>\n                time_key &#061; det[&#039;timestamp&#039;] \/\/ 10 * 10<\/p>\n<p>            if time_key not in time_groups:<br \/>\n                time_groups[time_key] &#061; []<br \/>\n            time_groups[time_key].append(det)<\/p>\n<p>        # \u4e3a\u6bcf\u4e2a\u65f6\u95f4\u7ec4\u521b\u5efa\u63a2\u6d4b\u9525\u4f53<br \/>\n        for time_key, time_detections in list(time_groups.items())[::3]:  # \u6bcf3\u7ec4\u663e\u793a\u4e00\u4e2a<br \/>\n            if not time_detections:<br \/>\n                continue<\/p>\n<p>            # \u8ba1\u7b97\u5e73\u5747\u65b9\u4f4d\u89d2\u548c\u4fef\u4ef0\u89d2<br \/>\n            azimuths &#061; [d[&#039;azimuth&#039;] for d in time_detections]<br \/>\n            elevations &#061; [d[&#039;elevation&#039;] for d in time_detections]<\/p>\n<p>            avg_azimuth &#061; np.mean(azimuths)<br \/>\n            avg_elevation &#061; np.mean(elevations)<\/p>\n<p>            # \u521b\u5efa\u63a2\u6d4b\u9525\u4f53<br \/>\n            cone_height &#061; max_range * 0.8<br \/>\n            cone_radius &#061; cone_height * math.tan(math.radians(5))  # 5\u5ea6\u6ce2\u675f\u5bbd\u5ea6<\/p>\n<p>            # \u521b\u5efa\u9525\u4f53&#xff08;\u6307\u5411\u5e73\u5747\u65b9\u5411&#xff09;<br \/>\n            cone &#061; pv.Cone(center&#061;radar_position, direction&#061;[1, 0, 0],<br \/>\n                          height&#061;cone_height, radius&#061;cone_radius)<\/p>\n<p>            # \u65cb\u8f6c\u5230\u6b63\u786e\u65b9\u5411<br \/>\n            cone.rotate_z(avg_azimuth, inplace&#061;True)<br \/>\n            cone.rotate_y(avg_elevation, inplace&#061;True)<\/p>\n<p>            # \u6dfb\u52a0\u9525\u4f53\u5230\u573a\u666f&#xff08;\u534a\u900f\u660e&#xff09;<br \/>\n            plotter.add_mesh(<br \/>\n                cone,<br \/>\n                color&#061;&#039;yellow&#039;,<br \/>\n                opacity&#061;0.1,<br \/>\n                style&#061;&#039;wireframe&#039; if len(time_detections) &gt; 1 else &#039;surface&#039;,<br \/>\n                name&#061;f&#039;detection_cone_{time_key}&#039;<br \/>\n            )<\/p>\n<p>    def add_time_animation(self, plotter, detections_by_target):<br \/>\n        &#034;&#034;&#034;\u6dfb\u52a0\u65f6\u95f4\u52a8\u753b\u63a7\u4ef6&#034;&#034;&#034;<br \/>\n        if not detections_by_target:<br \/>\n            return<\/p>\n<p>        # \u6536\u96c6\u6240\u6709\u65f6\u95f4\u70b9<br \/>\n        all_timestamps &#061; []<br \/>\n        for detections in detections_by_target.values():<br \/>\n            for det in detections:<br \/>\n                if isinstance(det[&#039;timestamp&#039;], datetime):<br \/>\n                    all_timestamps.append(det[&#039;timestamp&#039;].timestamp())<br \/>\n                else:<br \/>\n                    all_timestamps.append(det[&#039;timestamp&#039;])<\/p>\n<p>        if not all_timestamps:<br \/>\n            return<\/p>\n<p>        time_min &#061; min(all_timestamps)<br \/>\n        time_max &#061; max(all_timestamps)<\/p>\n<p>        # \u5f53\u524d\u65f6\u95f4\u6307\u9488<br \/>\n        self.current_animation_time &#061; time_min<br \/>\n        self.animation_speed &#061; 1.0  # \u5b9e\u65f6\u901f\u5ea6<br \/>\n        self.is_animating &#061; False<\/p>\n<p>        # \u65f6\u95f4\u6ed1\u5757<br \/>\n        def update_time_slider(value):<br \/>\n            self.current_animation_time &#061; value<br \/>\n            self._update_animation_frame(plotter, detections_by_target)<\/p>\n<p>        plotter.add_slider_widget(<br \/>\n            update_time_slider,<br \/>\n            [time_min, time_max],<br \/>\n            value&#061;time_min,<br \/>\n            title&#061;&#039;\u65f6\u95f4&#039;,<br \/>\n            pointa&#061;(0.7, 0.9),<br \/>\n            pointb&#061;(0.9, 0.9),<br \/>\n            style&#061;&#039;modern&#039;<br \/>\n        )<\/p>\n<p>        # \u64ad\u653e\/\u6682\u505c\u6309\u94ae<br \/>\n        def toggle_animation():<br \/>\n            self.is_animating &#061; not self.is_animating<br \/>\n            state &#061; &#034;\u64ad\u653e&#034; if self.is_animating else &#034;\u6682\u505c&#034;<br \/>\n            print(f&#034;\u52a8\u753b{state}&#034;)<\/p>\n<p>        plotter.add_checkbox_button_widget(<br \/>\n            toggle_animation,<br \/>\n            value&#061;False,<br \/>\n            position&#061;(10, 10),<br \/>\n            size&#061;30,<br \/>\n            color_on&#061;&#039;green&#039;,<br \/>\n            color_off&#061;&#039;red&#039;,<br \/>\n            background_color&#061;&#039;white&#039;<br \/>\n        )<\/p>\n<p>        # \u901f\u5ea6\u63a7\u5236<br \/>\n        def set_animation_speed(value):<br \/>\n            self.animation_speed &#061; value<br \/>\n            print(f&#034;\u52a8\u753b\u901f\u5ea6: {value}x&#034;)<\/p>\n<p>        plotter.add_slider_widget(<br \/>\n            set_animation_speed,<br \/>\n            [0.1, 5.0],<br \/>\n            value&#061;1.0,<br \/>\n            title&#061;&#039;\u901f\u5ea6&#039;,<br \/>\n            pointa&#061;(0.7, 0.8),<br \/>\n            pointb&#061;(0.9, 0.8),<br \/>\n            style&#061;&#039;modern&#039;<br \/>\n        )<\/p>\n<p>    def _update_animation_frame(self, plotter, detections_by_target):<br \/>\n        &#034;&#034;&#034;\u66f4\u65b0\u52a8\u753b\u5e27&#034;&#034;&#034;<br \/>\n        # \u6e05\u9664\u5f53\u524d\u5e27\u7684\u6807\u8bb0<br \/>\n        for actor_name in list(plotter.actors.keys()):<br \/>\n            if actor_name.startswith((&#039;current_detection_&#039;, &#039;time_marker_&#039;)):<br \/>\n                plotter.remove_actor(plotter.actors[actor_name])<\/p>\n<p>        # \u66f4\u65b0\u6bcf\u4e2a\u76ee\u6807\u7684\u5f53\u524d\u4f4d\u7f6e\u6807\u8bb0<br \/>\n        for target_id, detections in detections_by_target.items():<br \/>\n            # \u627e\u5230\u6700\u63a5\u8fd1\u5f53\u524d\u65f6\u95f4\u7684\u63a2\u6d4b\u70b9<br \/>\n            closest_det &#061; None<br \/>\n            min_time_diff &#061; float(&#039;inf&#039;)<\/p>\n<p>            for det in detections:<br \/>\n                if isinstance(det[&#039;timestamp&#039;], datetime):<br \/>\n                    det_time &#061; det[&#039;timestamp&#039;].timestamp()<br \/>\n                else:<br \/>\n                    det_time &#061; det[&#039;timestamp&#039;]<\/p>\n<p>                time_diff &#061; abs(det_time &#8211; self.current_animation_time)<br \/>\n                if time_diff &lt; min_time_diff:<br \/>\n                    min_time_diff &#061; time_diff<br \/>\n                    closest_det &#061; det<\/p>\n<p>            if closest_det and min_time_diff &lt; 30:  # 30\u79d2\u5185\u8ba4\u4e3a\u6709\u6548<br \/>\n                # \u521b\u5efa\u5f53\u524d\u4f4d\u7f6e\u6807\u8bb0<br \/>\n                marker &#061; pv.Sphere(center&#061;closest_det[&#039;position&#039;], radius&#061;100)<\/p>\n<p>                target_colors &#061; {<br \/>\n                    &#039;Target001&#039;: [1, 0, 0],<br \/>\n                    &#039;Target002&#039;: [0, 1, 0],<br \/>\n                    &#039;Target003&#039;: [0, 0, 1],<br \/>\n                    &#039;Target004&#039;: [1, 1, 0],<br \/>\n                }<\/p>\n<p>                color &#061; target_colors.get(target_id, [0.5, 0.5, 0.5])<\/p>\n<p>                plotter.add_mesh(<br \/>\n                    marker,<br \/>\n                    color&#061;color,<br \/>\n                    name&#061;f&#039;current_detection_{target_id}&#039;<br \/>\n                )<\/p>\n<p>                # \u6dfb\u52a0\u65f6\u95f4\u6807\u7b7e<br \/>\n                time_text &#061; closest_det[&#039;timestamp&#039;].strftime(&#039;%H:%M:%S&#039;) if isinstance(closest_det[&#039;timestamp&#039;], datetime) else str(closest_det[&#039;timestamp&#039;])<br \/>\n                label_text &#061; f&#034;{target_id}\\\\n{time_text}\\\\nSNR: {closest_det[&#039;snr&#039;]:.1f}dB&#034;<\/p>\n<p>                plotter.add_point_labels(<br \/>\n                    [closest_det[&#039;position&#039;]],<br \/>\n                    [label_text],<br \/>\n                    font_size&#061;8,<br \/>\n                    point_color&#061;color,<br \/>\n                    point_size&#061;0,<br \/>\n                    name&#061;f&#039;time_marker_{target_id}&#039;<br \/>\n                )<\/p>\n<p>        # \u66f4\u65b0\u65f6\u95f4\u663e\u793a<br \/>\n        if &#039;time_display&#039; in plotter.actors:<br \/>\n            plotter.remove_actor(plotter.actors[&#039;time_display&#039;])<\/p>\n<p>        current_time_str &#061; datetime.fromtimestamp(self.current_animation_time).strftime(&#039;%Y-%m-%d %H:%M:%S&#039;) if self.current_animation_time &gt; 0 else str(self.current_animation_time)<br \/>\n        time_text &#061; f&#034;\u5f53\u524d\u65f6\u95f4: {current_time_str}&#034;<\/p>\n<p>        plotter.add_text(<br \/>\n            time_text,<br \/>\n            position&#061;&#039;upper_center&#039;,<br \/>\n            font_size&#061;12,<br \/>\n            color&#061;&#039;white&#039;,<br \/>\n            name&#061;&#039;time_display&#039;<br \/>\n        )<\/p>\n<p>    def add_animation_callback(self, plotter, detections_by_target):<br \/>\n        &#034;&#034;&#034;\u6dfb\u52a0\u52a8\u753b\u56de\u8c03\u51fd\u6570&#034;&#034;&#034;<br \/>\n        self.last_animation_time &#061; time.time()<\/p>\n<p>        def animation_callback():<br \/>\n            if not self.is_animating:<br \/>\n                return<\/p>\n<p>            current_time &#061; time.time()<br \/>\n            delta_time &#061; current_time &#8211; self.last_animation_time<br \/>\n            self.last_animation_time &#061; current_time<\/p>\n<p>            # \u66f4\u65b0\u65f6\u95f4<br \/>\n            time_delta &#061; delta_time * self.animation_speed<br \/>\n            self.current_animation_time &#043;&#061; time_delta<\/p>\n<p>            # \u68c0\u67e5\u65f6\u95f4\u8303\u56f4<br \/>\n            all_timestamps &#061; []<br \/>\n            for detections in detections_by_target.values():<br \/>\n                for det in detections:<br \/>\n                    if isinstance(det[&#039;timestamp&#039;], datetime):<br \/>\n                        all_timestamps.append(det[&#039;timestamp&#039;].timestamp())<br \/>\n                    else:<br \/>\n                        all_timestamps.append(det[&#039;timestamp&#039;])<\/p>\n<p>            if all_timestamps:<br \/>\n                time_max &#061; max(all_timestamps)<br \/>\n                if self.current_animation_time &gt; time_max:<br \/>\n                    self.current_animation_time &#061; min(all_timestamps)  # \u5faa\u73af\u64ad\u653e<\/p>\n<p>            # \u66f4\u65b0\u5e27<br \/>\n            self._update_animation_frame(plotter, detections_by_target)<\/p>\n<p>        plotter.add_callback(animation_callback, interval&#061;50)  # 20fps<\/p>\n<p>    def create_statistics_panel(self, plotter, detections_by_target):<br \/>\n        &#034;&#034;&#034;\u521b\u5efa\u7edf\u8ba1\u4fe1\u606f\u9762\u677f&#034;&#034;&#034;<br \/>\n        if not detections_by_target:<br \/>\n            return<\/p>\n<p>        stats_text &#061; &#034;\u96f7\u8fbe\u63a2\u6d4b\u7edf\u8ba1\\\\n&#034;<br \/>\n        stats_text &#043;&#061; &#034;&#061;&#034; * 20 &#043; &#034;\\\\n&#034;<\/p>\n<p>        total_detections &#061; 0<br \/>\n        for target_id, detections in detections_by_target.items():<br \/>\n            stats_text &#043;&#061; f&#034;{target_id}:\\\\n&#034;<br \/>\n            stats_text &#043;&#061; f&#034;  \u63a2\u6d4b\u70b9\u6570: {len(detections)}\\\\n&#034;<\/p>\n<p>            if detections:<br \/>\n                snr_values &#061; [d[&#039;snr&#039;] for d in detections]<br \/>\n                distances &#061; [d[&#039;distance&#039;] for d in detections]<\/p>\n<p>                stats_text &#043;&#061; f&#034;  \u5e73\u5747SNR: {np.mean(snr_values):.1f}dB\\\\n&#034;<br \/>\n                stats_text &#043;&#061; f&#034;  \u5e73\u5747\u8ddd\u79bb: {np.mean(distances):.0f}m\\\\n&#034;<br \/>\n                stats_text &#043;&#061; f&#034;  \u8ddf\u8e2a\u7387: {sum(1 for d in detections if d[&#039;is_tracked&#039;])\/len(detections)*100:.1f}%\\\\n&#034;<\/p>\n<p>            stats_text &#043;&#061; &#034;\\\\n&#034;<br \/>\n            total_detections &#043;&#061; len(detections)<\/p>\n<p>        stats_text &#043;&#061; f&#034;\u603b\u63a2\u6d4b\u70b9: {total_detections}\\\\n&#034;<\/p>\n<p>        plotter.add_text(<br \/>\n            stats_text,<br \/>\n            position&#061;&#039;upper_left&#039;,<br \/>\n            font_size&#061;10,<br \/>\n            color&#061;&#039;white&#039;,<br \/>\n            name&#061;&#039;statistics_panel&#039;<br \/>\n        )<\/p>\n<p>    def create_visibility_controls(self, plotter):<br \/>\n        &#034;&#034;&#034;\u521b\u5efa\u53ef\u89c6\u5316\u63a7\u5236&#034;&#034;&#034;<br \/>\n        controls &#061; {<br \/>\n            &#039;show_detection_points&#039;: True,<br \/>\n            &#039;show_detection_lines&#039;: True,<br \/>\n            &#039;show_detection_cones&#039;: False,<br \/>\n            &#039;show_radar_model&#039;: True<br \/>\n        }<\/p>\n<p>        # \u63a2\u6d4b\u70b9\u663e\u793a\u63a7\u5236<br \/>\n        def toggle_detection_points(state):<br \/>\n            controls[&#039;show_detection_points&#039;] &#061; state<br \/>\n            self._update_visibility(plotter, controls)<\/p>\n<p>        plotter.add_checkbox_button_widget(<br \/>\n            toggle_detection_points,<br \/>\n            value&#061;True,<br \/>\n            position&#061;(10, 50),<br \/>\n            size&#061;25,<br \/>\n            color_on&#061;&#039;green&#039;,<br \/>\n            color_off&#061;&#039;red&#039;<br \/>\n        )<br \/>\n        plotter.add_text(&#034;\u63a2\u6d4b\u70b9&#034;, position&#061;(40, 50), font_size&#061;10, color&#061;&#039;white&#039;)<\/p>\n<p>        # \u63a2\u6d4b\u7ebf\u663e\u793a\u63a7\u5236<br \/>\n        def toggle_detection_lines(state):<br \/>\n            controls[&#039;show_detection_lines&#039;] &#061; state<br \/>\n            self._update_visibility(plotter, controls)<\/p>\n<p>        plotter.add_checkbox_button_widget(<br \/>\n            toggle_detection_lines,<br \/>\n            value&#061;True,<br \/>\n            position&#061;(10, 85),<br \/>\n            size&#061;25,<br \/>\n            color_on&#061;&#039;green&#039;,<br \/>\n            color_off&#061;&#039;red&#039;<br \/>\n        )<br \/>\n        plotter.add_text(&#034;\u63a2\u6d4b\u7ebf&#034;, position&#061;(40, 85), font_size&#061;10, color&#061;&#039;white&#039;)<\/p>\n<p>        # \u63a2\u6d4b\u9525\u4f53\u663e\u793a\u63a7\u5236<br \/>\n        def toggle_detection_cones(state):<br \/>\n            controls[&#039;show_detection_cones&#039;] &#061; state<br \/>\n            self._update_visibility(plotter, controls)<\/p>\n<p>        plotter.add_checkbox_button_widget(<br \/>\n            toggle_detection_cones,<br \/>\n            value&#061;False,<br \/>\n            position&#061;(10, 120),<br \/>\n            size&#061;25,<br \/>\n            color_on&#061;&#039;green&#039;,<br \/>\n            color_off&#061;&#039;red&#039;<br \/>\n        )<br \/>\n        plotter.add_text(&#034;\u63a2\u6d4b\u6ce2\u675f&#034;, position&#061;(40, 120), font_size&#061;10, color&#061;&#039;white&#039;)<\/p>\n<p>        # \u96f7\u8fbe\u6a21\u578b\u663e\u793a\u63a7\u5236<br \/>\n        def toggle_radar_model(state):<br \/>\n            controls[&#039;show_radar_model&#039;] &#061; state<br \/>\n            self._update_visibility(plotter, controls)<\/p>\n<p>        plotter.add_checkbox_button_widget(<br \/>\n            toggle_radar_model,<br \/>\n            value&#061;True,<br \/>\n            position&#061;(10, 155),<br \/>\n            size&#061;25,<br \/>\n            color_on&#061;&#039;green&#039;,<br \/>\n            color_off&#061;&#039;red&#039;<br \/>\n        )<br \/>\n        plotter.add_text(&#034;\u96f7\u8fbe\u6a21\u578b&#034;, position&#061;(40, 155), font_size&#061;10, color&#061;&#039;white&#039;)<\/p>\n<p>        return controls<\/p>\n<p>    def _update_visibility(self, plotter, controls):<br \/>\n        &#034;&#034;&#034;\u66f4\u65b0\u53ef\u89c6\u5316\u5143\u7d20\u663e\u793a\u72b6\u6001&#034;&#034;&#034;<br \/>\n        # \u66f4\u65b0\u63a2\u6d4b\u70b9\u663e\u793a<br \/>\n        for actor_name in list(plotter.actors.keys()):<br \/>\n            if actor_name.startswith(&#039;detection_points&#039;):<br \/>\n                plotter.actors[actor_name].SetVisibility(controls[&#039;show_detection_points&#039;])<\/p>\n<p>        # \u66f4\u65b0\u63a2\u6d4b\u7ebf\u663e\u793a<br \/>\n        for actor_name in list(plotter.actors.keys()):<br \/>\n            if actor_name.startswith(&#039;detection_line&#039;):<br \/>\n                plotter.actors[actor_name].SetVisibility(controls[&#039;show_detection_lines&#039;])<\/p>\n<p>        # \u66f4\u65b0\u63a2\u6d4b\u9525\u4f53\u663e\u793a<br \/>\n        for actor_name in list(plotter.actors.keys()):<br \/>\n            if actor_name.startswith(&#039;detection_cone&#039;):<br \/>\n                plotter.actors[actor_name].SetVisibility(controls[&#039;show_detection_cones&#039;])<\/p>\n<p>        # \u66f4\u65b0\u96f7\u8fbe\u6a21\u578b\u663e\u793a<br \/>\n        for actor_name in list(plotter.actors.keys()):<br \/>\n            if actor_name.startswith(&#039;radar_&#039;):<br \/>\n                plotter.actors[actor_name].SetVisibility(controls[&#039;show_radar_model&#039;])<\/p>\n<p>        plotter.update()<\/p>\n<p>    def run_demo(self, num_targets&#061;3):<br \/>\n        &#034;&#034;&#034;\u8fd0\u884c\u96f7\u8fbe\u63a2\u6d4b\u5386\u53f2\u53ef\u89c6\u5316\u6f14\u793a&#034;&#034;&#034;<br \/>\n        print(&#034;\u96f7\u8fbe\u63a2\u6d4b\u5386\u53f2\u53ef\u89c6\u5316\u6f14\u793a&#034;)<br \/>\n        print(&#034;&#061;&#034; * 50)<\/p>\n<p>        # \u52a0\u8f7d\u6570\u636e<br \/>\n        detection_count &#061; self.load_detection_data(num_targets)<br \/>\n        print(f&#034;\u52a0\u8f7d\u4e86 {detection_count} \u4e2a\u63a2\u6d4b\u70b9&#034;)<\/p>\n<p>        # \u6309\u76ee\u6807\u5206\u7ec4<br \/>\n        detections_by_target &#061; self.group_detections_by_target()<br \/>\n        print(f&#034;\u68c0\u6d4b\u5230 {len(detections_by_target)} \u4e2a\u76ee\u6807&#034;)<\/p>\n<p>        # \u521b\u5efa\u7ed8\u56fe\u7a97\u53e3<br \/>\n        self.plotter &#061; pv.Plotter(window_size&#061;(1600, 900),<br \/>\n                                 title&#061;&#034;\u96f7\u8fbe\u63a2\u6d4b\u5386\u53f2\u53ef\u89c6\u5316&#034;)<\/p>\n<p>        # \u8bbe\u7f6e\u573a\u666f<br \/>\n        self.plotter.set_background(&#039;linear_gradient&#039;, bottom&#061;&#039;#0a0a1a&#039;, top&#061;&#039;#1a1a2a&#039;)<br \/>\n        self.plotter.add_axes()<br \/>\n        self.plotter.show_grid()<\/p>\n<p>        # \u6dfb\u52a0\u5730\u5f62<br \/>\n        x &#061; np.linspace(-10000, 10000, 30)<br \/>\n        y &#061; np.linspace(-10000, 10000, 30)<br \/>\n        xx, yy &#061; np.meshgrid(x, y)<br \/>\n        z &#061; 100 &#043; 50 * np.sin(0.001 * xx) * np.cos(0.001 * yy)<br \/>\n        terrain &#061; pv.StructuredGrid(xx, yy, z)<br \/>\n        terrain[&#039;elevation&#039;] &#061; z.ravel()<\/p>\n<p>        self.plotter.add_mesh(<br \/>\n            terrain,<br \/>\n            cmap&#061;&#039;terrain&#039;,<br \/>\n            scalars&#061;&#039;elevation&#039;,<br \/>\n            opacity&#061;0.3,<br \/>\n            show_edges&#061;False,<br \/>\n            name&#061;&#039;terrain&#039;<br \/>\n        )<\/p>\n<p>        # \u6dfb\u52a0\u96f7\u8fbe\u6a21\u578b<br \/>\n        radar_scale &#061; 200<br \/>\n        self.create_radar_model(self.plotter, self.radar_position, radar_scale)<\/p>\n<p>        # \u6dfb\u52a0\u63a2\u6d4b\u70b9<br \/>\n        all_detections &#061; []<br \/>\n        for target_detections in detections_by_target.values():<br \/>\n            all_detections.extend(target_detections)<\/p>\n<p>        points_mesh &#061; self.create_detection_points(all_detections, self.plotter, &#039;snr&#039;)<br \/>\n        if points_mesh is not None:<br \/>\n            self.plotter.add_mesh(<br \/>\n                points_mesh,<br \/>\n                scalars&#061;&#039;snr&#039;,<br \/>\n                cmap&#061;&#039;hot&#039;,<br \/>\n                point_size&#061;8,<br \/>\n                render_points_as_spheres&#061;True,<br \/>\n                opacity&#061;0.8,<br \/>\n                name&#061;&#039;detection_points&#039;,<br \/>\n                show_scalar_bar&#061;True,<br \/>\n                scalar_bar_args&#061;{&#039;title&#039;: &#039;\u4fe1\u566a\u6bd4 (dB)&#039;}<br \/>\n            )<\/p>\n<p>        # \u6dfb\u52a0\u63a2\u6d4b\u8fde\u7ebf<br \/>\n        self.create_detection_lines(detections_by_target, self.plotter)<\/p>\n<p>        # \u6dfb\u52a0\u63a2\u6d4b\u9525\u4f53<br \/>\n        self.create_detection_cones(all_detections, self.plotter, self.radar_position)<\/p>\n<p>        # \u6dfb\u52a0\u65f6\u95f4\u52a8\u753b\u63a7\u4ef6<br \/>\n        self.add_time_animation(self.plotter, detections_by_target)<\/p>\n<p>        # \u6dfb\u52a0\u52a8\u753b\u56de\u8c03<br \/>\n        self.add_animation_callback(self.plotter, detections_by_target)<\/p>\n<p>        # \u6dfb\u52a0\u7edf\u8ba1\u4fe1\u606f\u9762\u677f<br \/>\n        self.create_statistics_panel(self.plotter, detections_by_target)<\/p>\n<p>        # \u6dfb\u52a0\u53ef\u89c6\u5316\u63a7\u5236<br \/>\n        controls &#061; self.create_visibility_controls(self.plotter)<\/p>\n<p>        # \u6dfb\u52a0\u63a7\u5236\u8bf4\u660e<br \/>\n        instructions &#061; &#034;\u63a7\u5236\u8bf4\u660e:\\\\n&#034;<br \/>\n        instructions &#043;&#061; &#034;\u5de6\u4fa7\u590d\u9009\u6846: \u663e\u793a\/\u9690\u85cf\u5143\u7d20\\\\n&#034;<br \/>\n        instructions &#043;&#061; &#034;\u65f6\u95f4\u6ed1\u5757: \u624b\u52a8\u63a7\u5236\u65f6\u95f4\\\\n&#034;<br \/>\n        instructions &#043;&#061; &#034;\u64ad\u653e\u6309\u94ae: \u5f00\u59cb\/\u6682\u505c\u52a8\u753b\\\\n&#034;<br \/>\n        instructions &#043;&#061; &#034;\u901f\u5ea6\u6ed1\u5757: \u8c03\u6574\u52a8\u753b\u901f\u5ea6\\\\n&#034;<br \/>\n        instructions &#043;&#061; &#034;\u9f20\u6807\u4ea4\u4e92: \u65cb\u8f6c\/\u5e73\u79fb\/\u7f29\u653e\u89c6\u89d2\\\\n&#034;<\/p>\n<p>        self.plotter.add_text(<br \/>\n            instructions,<br \/>\n            position&#061;&#039;lower_left&#039;,<br \/>\n            font_size&#061;10,<br \/>\n            color&#061;&#039;cyan&#039;,<br \/>\n            name&#061;&#039;instructions&#039;<br \/>\n        )<\/p>\n<p>        # \u8bbe\u7f6e\u76f8\u673a<br \/>\n        self.plotter.camera_position &#061; [<br \/>\n            (self.radar_position[0], self.radar_position[1] &#8211; 15000, 5000),<br \/>\n            self.radar_position,<br \/>\n            (0, 0, 1)<br \/>\n        ]<\/p>\n<p>        print(&#034;\\\\n\u6f14\u793a\u5df2\u542f\u52a8&#034;)<br \/>\n        print(&#034;\u4f7f\u7528\u5de6\u4fa7\u590d\u9009\u6846\u63a7\u5236\u4e0d\u540c\u5143\u7d20\u7684\u663e\u793a&#034;)<br \/>\n        print(&#034;\u4f7f\u7528\u65f6\u95f4\u63a7\u4ef6\u67e5\u770b\u4e0d\u540c\u65f6\u95f4\u7684\u63a2\u6d4b\u60c5\u51b5&#034;)<\/p>\n<p>        # \u663e\u793a<br \/>\n        self.plotter.show()<\/p>\n<p># \u8fd0\u884c\u6848\u4f8b3<br \/>\ndef run_case3():<br \/>\n    demo &#061; RadarDetectionVisualizer()<br \/>\n    demo.run_demo(num_targets&#061;3)<\/p>\n<p>if __name__ &#061;&#061; &#034;__main__&#034;:<br \/>\n    run_case3()<\/p>\n<h3>7. \u77e5\u8bc6\u70b9\u603b\u7ed3\u4e0e\u6269\u5c55\u5e94\u7528<\/h3>\n<h4>7.1 \u6838\u5fc3\u6280\u672f\u8981\u70b9\u603b\u7ed3<\/h4>\n<p>1. \u8f68\u8ff9\u6570\u636e\u5904\u7406\u6280\u672f<\/p>\n<ul>\n<li>\n<p>\u591a\u683c\u5f0f\u6570\u636e\u52a0\u8f7d&#xff08;CSV\u3001JSON\u3001\u6570\u636e\u5e93&#xff09;<\/p>\n<\/li>\n<li>\n<p>\u5750\u6807\u7cfb\u7edf\u8f6c\u6362\u4e0e\u6570\u636e\u6807\u51c6\u5316<\/p>\n<\/li>\n<li>\n<p>\u65f6\u95f4\u5e8f\u5217\u6570\u636e\u5904\u7406\u4e0e\u63d2\u503c<\/p>\n<\/li>\n<li>\n<p>\u8f68\u8ff9\u4f18\u5316\u4e0e\u91c7\u6837\u7b97\u6cd5<\/p>\n<\/li>\n<\/ul>\n<p>2. 3D\u53ef\u89c6\u5316\u6280\u672f<\/p>\n<ul>\n<li>\n<p>\u591a\u79cd\u8f68\u8ff9\u8868\u793a\u65b9\u6cd5&#xff08;\u7ebf\u3001\u6837\u6761\u3001\u7ba1\u9053&#xff09;<\/p>\n<\/li>\n<li>\n<p>\u989c\u8272\u6620\u5c04\u4e0e\u5c5e\u6027\u53ef\u89c6\u5316<\/p>\n<\/li>\n<li>\n<p>\u52a8\u6001\u65f6\u95f4\u8f74\u4e0e\u52a8\u753b\u63a7\u5236<\/p>\n<\/li>\n<li>\n<p>\u4ea4\u4e92\u5f0f\u63a7\u4ef6\u4e0e\u7528\u6237\u754c\u9762<\/p>\n<\/li>\n<\/ul>\n<p>3. \u96f7\u8fbe\u63a2\u6d4b\u53ef\u89c6\u5316<\/p>\n<ul>\n<li>\n<p>\u63a2\u6d4b\u70b9\u4e91\u7684\u53ef\u89c6\u5316<\/p>\n<\/li>\n<li>\n<p>\u96f7\u8fbe\u6ce2\u675f\u4e0e\u63a2\u6d4b\u8303\u56f4\u8868\u793a<\/p>\n<\/li>\n<li>\n<p>\u591a\u76ee\u6807\u8ddf\u8e2a\u4e0e\u8f68\u8ff9\u91cd\u5efa<\/p>\n<\/li>\n<li>\n<p>\u63a2\u6d4b\u8d28\u91cf&#xff08;SNR&#xff09;\u7684\u53ef\u89c6\u5316<\/p>\n<\/li>\n<\/ul>\n<h4>7.2 \u6027\u80fd\u4f18\u5316\u6280\u5de7<\/h4>\n<p>\u5927\u89c4\u6a21\u8f68\u8ff9\u6570\u636e\u5904\u7406<\/p>\n<p># 1. \u6570\u636e\u5206\u5757\u52a0\u8f7d<br \/>\ndef load_large_trajectory_chunked(filepath, chunk_size&#061;10000):<br \/>\n    &#034;&#034;&#034;\u5206\u5757\u52a0\u8f7d\u5927\u89c4\u6a21\u8f68\u8ff9\u6570\u636e&#034;&#034;&#034;<br \/>\n    chunks &#061; []<br \/>\n    for chunk in pd.read_csv(filepath, chunksize&#061;chunk_size):<br \/>\n        # \u5904\u7406\u6bcf\u4e2a\u6570\u636e\u5757<br \/>\n        processed_chunk &#061; process_trajectory_chunk(chunk)<br \/>\n        chunks.append(processed_chunk)<br \/>\n    return pd.concat(chunks)<\/p>\n<p># 2. \u7ec6\u8282\u5c42\u6b21&#xff08;LOD&#xff09;\u6280\u672f<br \/>\ndef create_lod_trajectory(trajectory, lod_levels&#061;3):<br \/>\n    &#034;&#034;&#034;\u521b\u5efa\u591a\u7ec6\u8282\u5c42\u6b21\u8f68\u8ff9&#034;&#034;&#034;<br \/>\n    lod_meshes &#061; []<br \/>\n    for level in range(lod_levels):<br \/>\n        if level &#061;&#061; 0:  # \u6700\u9ad8\u7ec6\u8282<br \/>\n            mesh &#061; create_high_detail_trajectory(trajectory)<br \/>\n        else:  # \u8f83\u4f4e\u7ec6\u8282<br \/>\n            sample_ratio &#061; 1.0 \/ (2 ** level)<br \/>\n            sampled_points &#061; uniform_sampling(trajectory.points, sample_ratio)<br \/>\n            mesh &#061; create_simplified_trajectory(sampled_points)<br \/>\n        lod_meshes.append(mesh)<br \/>\n    return lod_meshes<\/p>\n<p>\u5b9e\u65f6\u6e32\u67d3\u4f18\u5316<\/p>\n<p># 1. \u5b9e\u4f8b\u5316\u6e32\u67d3<br \/>\ndef render_multiple_trajectories_instanced(trajectories):<br \/>\n    &#034;&#034;&#034;\u4f7f\u7528\u5b9e\u4f8b\u5316\u6e32\u67d3\u591a\u4e2a\u76f8\u4f3c\u8f68\u8ff9&#034;&#034;&#034;<br \/>\n    base_mesh &#061; create_base_trajectory_mesh()<br \/>\n    instance_matrices &#061; []<\/p>\n<p>    for traj in trajectories:<br \/>\n        # \u8ba1\u7b97\u53d8\u6362\u77e9\u9635<br \/>\n        matrix &#061; calculate_transform_matrix(traj)<br \/>\n        instance_matrices.append(matrix)<\/p>\n<p>    # \u6279\u91cf\u6e32\u67d3<br \/>\n    plotter.add_mesh(base_mesh, transforms&#061;instance_matrices)<\/p>\n<p># 2. \u89c6\u9525\u4f53\u88c1\u526a<br \/>\ndef frustum_culling(trajectories, camera_frustum):<br \/>\n    &#034;&#034;&#034;\u89c6\u9525\u4f53\u88c1\u526a&#xff0c;\u53ea\u6e32\u67d3\u53ef\u89c1\u8f68\u8ff9&#034;&#034;&#034;<br \/>\n    visible_trajectories &#061; []<br \/>\n    for traj in trajectories:<br \/>\n        if is_trajectory_in_frustum(traj, camera_frustum):<br \/>\n            visible_trajectories.append(traj)<br \/>\n    return visible_trajectories<\/p>\n<h4>7.3 \u6269\u5c55\u5e94\u7528\u65b9\u5411<\/h4>\n<p>\u519b\u4e8b\u4eff\u771f\u5e94\u7528<\/p>\n<ul>\n<li>\n<p>\u6218\u573a\u6001\u52bf\u5b9e\u65f6\u53ef\u89c6\u5316<\/p>\n<\/li>\n<li>\n<p>\u5bfc\u5f39\u9632\u5fa1\u7cfb\u7edf\u6a21\u62df<\/p>\n<\/li>\n<li>\n<p>\u7535\u5b50\u5bf9\u6297\u6548\u679c\u8bc4\u4f30<\/p>\n<\/li>\n<li>\n<p>\u4f5c\u6218\u65b9\u6848\u63a8\u6f14\u9a8c\u8bc1<\/p>\n<\/li>\n<\/ul>\n<p>\u6c11\u7528\u9886\u57df\u5e94\u7528<\/p>\n<ul>\n<li>\n<p>\u7a7a\u4e2d\u4ea4\u901a\u7ba1\u5236\u7cfb\u7edf<\/p>\n<\/li>\n<li>\n<p>\u65e0\u4eba\u673a\u822a\u8ff9\u76d1\u63a7<\/p>\n<\/li>\n<li>\n<p>\u8f66\u8f86\u8f68\u8ff9\u5206\u6790<\/p>\n<\/li>\n<li>\n<p>\u8fd0\u52a8\u76ee\u6807\u884c\u4e3a\u5206\u6790<\/p>\n<\/li>\n<\/ul>\n<p>\u79d1\u5b66\u7814\u7a76\u5e94\u7528<\/p>\n<ul>\n<li>\n<p>\u52a8\u7269\u8fc1\u5f99\u8f68\u8ff9\u7814\u7a76<\/p>\n<\/li>\n<li>\n<p>\u6c14\u8c61\u6570\u636e\u53ef\u89c6\u5316<\/p>\n<\/li>\n<li>\n<p>\u6d77\u6d0b\u6d0b\u6d41\u5206\u6790<\/p>\n<\/li>\n<li>\n<p>\u5929\u4f53\u8fd0\u52a8\u8f68\u8ff9\u6a21\u62df<\/p>\n<\/li>\n<\/ul>\n<h3>8. \u7ed3\u8bed<\/h3>\n<p>\u672c\u6587\u4ecb\u7ecd\u4e86\u4f7f\u7528PyVista\u8fdb\u884c\u96f7\u8fbe\u4e0e\u76ee\u6807\u8f68\u8ff9\u53ef\u89c6\u5316\u7684\u5b8c\u6574\u6280\u672f\u65b9\u6848&#xff0c;\u6db5\u76d6\u4e86\u4ece\u6570\u636e\u52a0\u8f7d\u5904\u7406\u5230\u9ad8\u7ea7\u53ef\u89c6\u5316\u7684\u5168\u6d41\u7a0b\u3002\u901a\u8fc7\u4e09\u4e2a\u5b9e\u6218\u6848\u4f8b&#xff0c;\u6211\u4eec\u5c55\u793a\u4e86&#xff1a;<\/p>\n<li>\n<p>\u5355\u76ee\u6807\u98de\u884c\u8f68\u8ff9\u53ef\u89c6\u5316\u200b &#8211; \u57fa\u7840\u8f68\u8ff9\u8868\u793a\u4e0e\u5c5e\u6027\u6620\u5c04<\/p>\n<\/li>\n<li>\n<p>\u591a\u76ee\u6807\u8f68\u8ff9\u5bf9\u6bd4\u5206\u6790\u200b &#8211; \u590d\u6742\u573a\u666f\u4e0b\u7684\u8f68\u8ff9\u7ba1\u7406\u4e0e\u6bd4\u8f83<\/p>\n<\/li>\n<li>\n<p>\u96f7\u8fbe\u63a2\u6d4b\u5386\u53f2\u53ef\u89c6\u5316\u200b &#8211; \u65f6\u95f4\u5e8f\u5217\u6570\u636e\u7684\u52a8\u6001\u5c55\u793a<\/p>\n<\/li>\n<p>\u8fd9\u4e9b\u6280\u672f\u4e0d\u4ec5\u9002\u7528\u4e8e\u519b\u4e8b\u4eff\u771f\u9886\u57df&#xff0c;\u5728\u4ea4\u901a\u76d1\u63a7\u3001\u73af\u5883\u76d1\u6d4b\u3001\u79d1\u5b66\u7814\u7a76\u7b49\u4f17\u591a\u9886\u57df\u90fd\u6709\u5e7f\u6cdb\u5e94\u7528\u4ef7\u503c\u3002PyVista\u4f5c\u4e3a\u5f3a\u5927\u76843D\u53ef\u89c6\u5316\u5de5\u5177&#xff0c;\u4e3a\u8f68\u8ff9\u6570\u636e\u7684\u76f4\u89c2\u7406\u89e3\u548c\u6df1\u5ea6\u5206\u6790\u63d0\u4f9b\u4e86\u6709\u529b\u652f\u6301\u3002<\/p>\n<p>\u968f\u7740\u6570\u636e\u89c4\u6a21\u7684\u4e0d\u65ad\u6269\u5927\u548c\u5206\u6790\u9700\u6c42\u7684\u65e5\u76ca\u590d\u6742&#xff0c;\u8f68\u8ff9\u53ef\u89c6\u5316\u6280\u672f\u5c06\u7ee7\u7eed\u5411\u5b9e\u65f6\u5316\u3001\u667a\u80fd\u5316\u3001\u4ea4\u4e92\u5f0f\u65b9\u5411\u53d1\u5c55\u3002\u638c\u63e1\u8fd9\u4e9b\u6838\u5fc3\u6280\u672f&#xff0c;\u5c06\u4e3a\u5e94\u5bf9\u672a\u6765\u7684\u6570\u636e\u53ef\u89c6\u5316\u6311\u6218\u5960\u5b9a\u575a\u5b9e\u57fa\u7840\u3002<\/p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u6458\u8981\u5728\u519b\u4e8b\u4eff\u771f\u3001\u7a7a\u4e2d\u4ea4\u901a\u7ba1\u5236\u3001\u65e0\u4eba\u673a\u76d1\u63a7\u7b49\u9886\u57df&#xff0c;\u8f68\u8ff9\u6570\u636e\u7684\u53ef\u89c6\u5316\u662f\u7406\u89e3\u76ee\u6807\u884c\u4e3a\u3001\u5206\u6790\u8fd0\u52a8\u89c4\u5f8b\u3001\u8bc4\u4f30\u6218\u672f\u6548\u679c\u7684\u5173\u952e\u3002\u672c\u6587\u662f\\&#8221;PyVista\u96f7\u8fbe\u7535\u5b50\u5bf9\u6297\u6218\u573a\u6001\u52bf\u4eff\u771f\\&#8221;\u7cfb\u5217\u7684\u7b2c\u4e09\u7bc7&#xff0c;\u4e13\u6ce8\u4e8e\u96f7\u8fbe\u4e0e\u76ee\u6807\u8f68\u8ff9\u76843D\u53ef\u89c6\u5316\u6280\u672f\u3002\u6211\u4eec\u5c06\u6df1\u5165\u63a2\u8ba8\u5982\u4f55\u4ece\u5404\u79cd\u6570\u636e\u6e90\u52a0\u8f7d\u8f68\u8ff9\u6570\u636e&#xff0c;\u57283D\u7a7a\u95f4\u4e2d\u521b\u5efa\u76f4\u89c2\u7684\u52a8\u6001\u8f68\u8ff9\u5c55\u793a&#xff0c;\u4ee5\u53ca\u5982\u4f55\u901a\u8fc7\u989c\u8272\u6620\u5c04\u3001\u65f6\u95f4\u6807\u7b7e\u3001\u591a\u7ef4\u5ea6\u4fe1\u606f\u53e0\u52a0\u7b49\u65b9\u5f0f\u589e\u5f3a\u8f68\u8ff9\u7684\u53ef\u8bfb\u6027\u548c\u4fe1\u606f\u5bc6\u5ea6\u3002\u901a\u8fc7\u4e09\u4e2a\u5b8c\u6574\u7684\u5b9e\u6218\u6848\u4f8b&#xff0c;\u8bfb\u8005\u5c06\u638c\u63e1\u8f68<\/p>\n","protected":false},"author":2,"featured_media":63256,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[6688,81,5936,50,190,207,442],"topic":[],"class_list":["post-63257","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-server","tag-gui","tag-python","tag-tkinter","tag-50","tag-190","tag-207","tag-442"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v20.3 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>PyVista\u6218\u573a\u53ef\u89c6\u5316\u5b9e\u6218\uff08\u4e09\uff09\uff1a\u96f7\u8fbe\u4e0e\u76ee\u6807\u8f68\u8ff9\u53ef\u89c6\u5316 - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.wsisp.com\/helps\/63257.html\" \/>\n<meta property=\"og:locale\" content=\"zh_CN\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"PyVista\u6218\u573a\u53ef\u89c6\u5316\u5b9e\u6218\uff08\u4e09\uff09\uff1a\u96f7\u8fbe\u4e0e\u76ee\u6807\u8f68\u8ff9\u53ef\u89c6\u5316 - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3\" \/>\n<meta property=\"og:description\" content=\"\u6458\u8981\u5728\u519b\u4e8b\u4eff\u771f\u3001\u7a7a\u4e2d\u4ea4\u901a\u7ba1\u5236\u3001\u65e0\u4eba\u673a\u76d1\u63a7\u7b49\u9886\u57df&#xff0c;\u8f68\u8ff9\u6570\u636e\u7684\u53ef\u89c6\u5316\u662f\u7406\u89e3\u76ee\u6807\u884c\u4e3a\u3001\u5206\u6790\u8fd0\u52a8\u89c4\u5f8b\u3001\u8bc4\u4f30\u6218\u672f\u6548\u679c\u7684\u5173\u952e\u3002\u672c\u6587\u662f&quot;PyVista\u96f7\u8fbe\u7535\u5b50\u5bf9\u6297\u6218\u573a\u6001\u52bf\u4eff\u771f&quot;\u7cfb\u5217\u7684\u7b2c\u4e09\u7bc7&#xff0c;\u4e13\u6ce8\u4e8e\u96f7\u8fbe\u4e0e\u76ee\u6807\u8f68\u8ff9\u76843D\u53ef\u89c6\u5316\u6280\u672f\u3002\u6211\u4eec\u5c06\u6df1\u5165\u63a2\u8ba8\u5982\u4f55\u4ece\u5404\u79cd\u6570\u636e\u6e90\u52a0\u8f7d\u8f68\u8ff9\u6570\u636e&#xff0c;\u57283D\u7a7a\u95f4\u4e2d\u521b\u5efa\u76f4\u89c2\u7684\u52a8\u6001\u8f68\u8ff9\u5c55\u793a&#xff0c;\u4ee5\u53ca\u5982\u4f55\u901a\u8fc7\u989c\u8272\u6620\u5c04\u3001\u65f6\u95f4\u6807\u7b7e\u3001\u591a\u7ef4\u5ea6\u4fe1\u606f\u53e0\u52a0\u7b49\u65b9\u5f0f\u589e\u5f3a\u8f68\u8ff9\u7684\u53ef\u8bfb\u6027\u548c\u4fe1\u606f\u5bc6\u5ea6\u3002\u901a\u8fc7\u4e09\u4e2a\u5b8c\u6574\u7684\u5b9e\u6218\u6848\u4f8b&#xff0c;\u8bfb\u8005\u5c06\u638c\u63e1\u8f68\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.wsisp.com\/helps\/63257.html\" \/>\n<meta property=\"og:site_name\" content=\"\u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3\" \/>\n<meta property=\"article:published_time\" content=\"2026-01-21T10:16:56+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.wsisp.com\/helps\/wp-content\/uploads\/2026\/01\/20260121101654-6970a79652ca0.png\" \/>\n<meta name=\"author\" content=\"admin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"\u4f5c\u8005\" \/>\n\t<meta name=\"twitter:data1\" content=\"admin\" \/>\n\t<meta name=\"twitter:label2\" content=\"\u9884\u8ba1\u9605\u8bfb\u65f6\u95f4\" \/>\n\t<meta name=\"twitter:data2\" content=\"35 \u5206\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/63257.html\",\"url\":\"https:\/\/www.wsisp.com\/helps\/63257.html\",\"name\":\"PyVista\u6218\u573a\u53ef\u89c6\u5316\u5b9e\u6218\uff08\u4e09\uff09\uff1a\u96f7\u8fbe\u4e0e\u76ee\u6807\u8f68\u8ff9\u53ef\u89c6\u5316 - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3\",\"isPartOf\":{\"@id\":\"https:\/\/www.wsisp.com\/helps\/#website\"},\"datePublished\":\"2026-01-21T10:16:56+00:00\",\"dateModified\":\"2026-01-21T10:16:56+00:00\",\"author\":{\"@id\":\"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/358e386c577a3ab51c4493330a20ad41\"},\"breadcrumb\":{\"@id\":\"https:\/\/www.wsisp.com\/helps\/63257.html#breadcrumb\"},\"inLanguage\":\"zh-Hans\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.wsisp.com\/helps\/63257.html\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/63257.html#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"\u9996\u9875\",\"item\":\"https:\/\/www.wsisp.com\/helps\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"PyVista\u6218\u573a\u53ef\u89c6\u5316\u5b9e\u6218\uff08\u4e09\uff09\uff1a\u96f7\u8fbe\u4e0e\u76ee\u6807\u8f68\u8ff9\u53ef\u89c6\u5316\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/#website\",\"url\":\"https:\/\/www.wsisp.com\/helps\/\",\"name\":\"\u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3\",\"description\":\"\u9999\u6e2f\u670d\u52a1\u5668_\u9999\u6e2f\u4e91\u670d\u52a1\u5668\u8d44\u8baf_\u670d\u52a1\u5668\u5e2e\u52a9\u6587\u6863_\u670d\u52a1\u5668\u6559\u7a0b\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.wsisp.com\/helps\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"zh-Hans\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/358e386c577a3ab51c4493330a20ad41\",\"name\":\"admin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"zh-Hans\",\"@id\":\"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/gravatar.wp-china-yes.net\/avatar\/?s=96&d=mystery\",\"contentUrl\":\"https:\/\/gravatar.wp-china-yes.net\/avatar\/?s=96&d=mystery\",\"caption\":\"admin\"},\"sameAs\":[\"http:\/\/wp.wsisp.com\"],\"url\":\"https:\/\/www.wsisp.com\/helps\/author\/admin\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"PyVista\u6218\u573a\u53ef\u89c6\u5316\u5b9e\u6218\uff08\u4e09\uff09\uff1a\u96f7\u8fbe\u4e0e\u76ee\u6807\u8f68\u8ff9\u53ef\u89c6\u5316 - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.wsisp.com\/helps\/63257.html","og_locale":"zh_CN","og_type":"article","og_title":"PyVista\u6218\u573a\u53ef\u89c6\u5316\u5b9e\u6218\uff08\u4e09\uff09\uff1a\u96f7\u8fbe\u4e0e\u76ee\u6807\u8f68\u8ff9\u53ef\u89c6\u5316 - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","og_description":"\u6458\u8981\u5728\u519b\u4e8b\u4eff\u771f\u3001\u7a7a\u4e2d\u4ea4\u901a\u7ba1\u5236\u3001\u65e0\u4eba\u673a\u76d1\u63a7\u7b49\u9886\u57df&#xff0c;\u8f68\u8ff9\u6570\u636e\u7684\u53ef\u89c6\u5316\u662f\u7406\u89e3\u76ee\u6807\u884c\u4e3a\u3001\u5206\u6790\u8fd0\u52a8\u89c4\u5f8b\u3001\u8bc4\u4f30\u6218\u672f\u6548\u679c\u7684\u5173\u952e\u3002\u672c\u6587\u662f\"PyVista\u96f7\u8fbe\u7535\u5b50\u5bf9\u6297\u6218\u573a\u6001\u52bf\u4eff\u771f\"\u7cfb\u5217\u7684\u7b2c\u4e09\u7bc7&#xff0c;\u4e13\u6ce8\u4e8e\u96f7\u8fbe\u4e0e\u76ee\u6807\u8f68\u8ff9\u76843D\u53ef\u89c6\u5316\u6280\u672f\u3002\u6211\u4eec\u5c06\u6df1\u5165\u63a2\u8ba8\u5982\u4f55\u4ece\u5404\u79cd\u6570\u636e\u6e90\u52a0\u8f7d\u8f68\u8ff9\u6570\u636e&#xff0c;\u57283D\u7a7a\u95f4\u4e2d\u521b\u5efa\u76f4\u89c2\u7684\u52a8\u6001\u8f68\u8ff9\u5c55\u793a&#xff0c;\u4ee5\u53ca\u5982\u4f55\u901a\u8fc7\u989c\u8272\u6620\u5c04\u3001\u65f6\u95f4\u6807\u7b7e\u3001\u591a\u7ef4\u5ea6\u4fe1\u606f\u53e0\u52a0\u7b49\u65b9\u5f0f\u589e\u5f3a\u8f68\u8ff9\u7684\u53ef\u8bfb\u6027\u548c\u4fe1\u606f\u5bc6\u5ea6\u3002\u901a\u8fc7\u4e09\u4e2a\u5b8c\u6574\u7684\u5b9e\u6218\u6848\u4f8b&#xff0c;\u8bfb\u8005\u5c06\u638c\u63e1\u8f68","og_url":"https:\/\/www.wsisp.com\/helps\/63257.html","og_site_name":"\u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","article_published_time":"2026-01-21T10:16:56+00:00","og_image":[{"url":"https:\/\/www.wsisp.com\/helps\/wp-content\/uploads\/2026\/01\/20260121101654-6970a79652ca0.png"}],"author":"admin","twitter_card":"summary_large_image","twitter_misc":{"\u4f5c\u8005":"admin","\u9884\u8ba1\u9605\u8bfb\u65f6\u95f4":"35 \u5206"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.wsisp.com\/helps\/63257.html","url":"https:\/\/www.wsisp.com\/helps\/63257.html","name":"PyVista\u6218\u573a\u53ef\u89c6\u5316\u5b9e\u6218\uff08\u4e09\uff09\uff1a\u96f7\u8fbe\u4e0e\u76ee\u6807\u8f68\u8ff9\u53ef\u89c6\u5316 - \u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","isPartOf":{"@id":"https:\/\/www.wsisp.com\/helps\/#website"},"datePublished":"2026-01-21T10:16:56+00:00","dateModified":"2026-01-21T10:16:56+00:00","author":{"@id":"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/358e386c577a3ab51c4493330a20ad41"},"breadcrumb":{"@id":"https:\/\/www.wsisp.com\/helps\/63257.html#breadcrumb"},"inLanguage":"zh-Hans","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.wsisp.com\/helps\/63257.html"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.wsisp.com\/helps\/63257.html#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"\u9996\u9875","item":"https:\/\/www.wsisp.com\/helps"},{"@type":"ListItem","position":2,"name":"PyVista\u6218\u573a\u53ef\u89c6\u5316\u5b9e\u6218\uff08\u4e09\uff09\uff1a\u96f7\u8fbe\u4e0e\u76ee\u6807\u8f68\u8ff9\u53ef\u89c6\u5316"}]},{"@type":"WebSite","@id":"https:\/\/www.wsisp.com\/helps\/#website","url":"https:\/\/www.wsisp.com\/helps\/","name":"\u7f51\u7855\u4e92\u8054\u5e2e\u52a9\u4e2d\u5fc3","description":"\u9999\u6e2f\u670d\u52a1\u5668_\u9999\u6e2f\u4e91\u670d\u52a1\u5668\u8d44\u8baf_\u670d\u52a1\u5668\u5e2e\u52a9\u6587\u6863_\u670d\u52a1\u5668\u6559\u7a0b","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.wsisp.com\/helps\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"zh-Hans"},{"@type":"Person","@id":"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/358e386c577a3ab51c4493330a20ad41","name":"admin","image":{"@type":"ImageObject","inLanguage":"zh-Hans","@id":"https:\/\/www.wsisp.com\/helps\/#\/schema\/person\/image\/","url":"https:\/\/gravatar.wp-china-yes.net\/avatar\/?s=96&d=mystery","contentUrl":"https:\/\/gravatar.wp-china-yes.net\/avatar\/?s=96&d=mystery","caption":"admin"},"sameAs":["http:\/\/wp.wsisp.com"],"url":"https:\/\/www.wsisp.com\/helps\/author\/admin"}]}},"_links":{"self":[{"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/posts\/63257","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/comments?post=63257"}],"version-history":[{"count":0,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/posts\/63257\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/media\/63256"}],"wp:attachment":[{"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/media?parent=63257"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/categories?post=63257"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/tags?post=63257"},{"taxonomy":"topic","embeddable":true,"href":"https:\/\/www.wsisp.com\/helps\/wp-json\/wp\/v2\/topic?post=63257"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}