话不多说直接上代码,库直接自己装,主播懒死了,记得替换成自己的shape文件
实现shp文件并排比对,也可以是更多的视图自己调整,拖动一个视图,另一个视图同步移动有待实现
"""
Shapefile多视图可视化
使用GeoPandas读取数据,VTK进行三角化处理,Trame实现多视图交互
两个视图分别展示不同的shp文件
"""
from trame.app import get_server
from trame.widgets import vuetify, vtk as vtk_widgets
from trame.ui.vuetify import SinglePageLayout
import geopandas as gpd # 读取shp文件
import numpy as np
from vtkmodules.vtkCommonCore import vtkPoints
from vtkmodules.vtkCommonDataModel import vtkPolyData, vtkCellArray
from vtkmodules.vtkFiltersCore import vtkTriangleFilter # 三角化后用这个读取器读取
from vtkmodules.vtkRenderingCore import (
vtkRenderer,
vtkRenderWindow,
vtkRenderWindowInteractor,
vtkPolyDataMapper,
vtkActor,
)
# VTK初始化
from vtkmodules.vtkInteractionStyle import vtkInteractorStyleSwitch # noqa
import vtkmodules.vtkRenderingOpenGL2 # noqa
# -----------------------------------------------------------------------------
# Trame初始化
# -----------------------------------------------------------------------------
server = get_server(client_type="vue2")
state, ctrl = server.state, server.controller
state.trame__title = "双vtp视图对比"
state.layout_mode = "horizontal" # 默认水平布局
# -----------------------------------------------------------------------------
# 数据处理函数
# -----------------------------------------------------------------------------
def shp_to_vtk_polygon(gdf):
"""将GeoDataFrame转换为VTK多边形数据"""
points = vtkPoints()
polys = vtkCellArray()
for geom in gdf.geometry:
if geom.geom_type == 'Polygon':
coords = np.array(geom.exterior.coords)
elif geom.geom_type == 'MultiPolygon':
coords = np.concatenate([np.array(p.exterior.coords) for p in geom.geoms])
else:
continue
point_ids = []
for coord in coords:
point_ids.append(points.InsertNextPoint(coord[0], coord[1], 0))
polys.InsertNextCell(len(point_ids), point_ids)
poly_data = vtkPolyData()
poly_data.SetPoints(points)
poly_data.SetPolys(polys)
return poly_data
def load_and_process_shapefile(file_path, z_scale=1.0):
"""使用GeoPandas加载并处理Shapefile"""
gdf = gpd.read_file(file_path)
poly_data = shp_to_vtk_polygon(gdf)
# 如果有Z坐标,可以在这里进行缩放
points = poly_data.GetPoints()
for i in range(points.GetNumberOfPoints()):
x, y, z = points.GetPoint(i)
points.SetPoint(i, x, y, z * z_scale)
triangle_filter = vtkTriangleFilter()
triangle_filter.SetInputData(poly_data)
triangle_filter.PassLinesOn()
triangle_filter.PassVertsOn()
triangle_filter.Update()
return triangle_filter.GetOutput()
# -----------------------------------------------------------------------------
# 主程序
# -----------------------------------------------------------------------------
# 读取和处理两个不同的Shapefile
SHAPEFILE_PATHS = [
r"D:\Data\1\1.shp", # 替换为第一个文件的实际路径
r"D:\Data\1\1.shp", # 替换为第二个文件的实际路径
]
# 为每个文件创建不同的视图配置
VIEW_CONFIGS = [
{
"name": "Shapefile 1",
"background": (0.9, 0.9, 0.9),
"color": (0.2, 0.4, 0.8),
"file_path": SHAPEFILE_PATHS[0],
"z_scale": 1.0
},
{
"name": "Shapefile 2",
"background": (0.8, 0.9, 1.0),
"color": (0.4, 0.2, 0.6),
"file_path": SHAPEFILE_PATHS[1],
"z_scale": 1.0
},
]
render_windows = []
html_views = []
for config in VIEW_CONFIGS:
# 处理数据
processed_data = load_and_process_shapefile(config["file_path"], config["z_scale"])
# 创建Mapper
mapper = vtkPolyDataMapper()
mapper.SetInputData(processed_data)
# 创建Actor
actor = vtkActor()
actor.SetMapper(mapper)
actor.GetProperty().SetColor(config["color"])
actor.GetProperty().SetLineWidth(1.5)
# 创建渲染器和窗口
renderer = vtkRenderer()
renderer.SetBackground(config["background"])
render_window = vtkRenderWindow()
render_window.AddRenderer(renderer)
render_window.SetWindowName(config["name"])
# 设置交互方式
render_window_interactor = vtkRenderWindowInteractor()
render_window_interactor.SetRenderWindow(render_window)
render_window_interactor.GetInteractorStyle().SetCurrentStyleToTrackballCamera()
# 添加Actor并初始化视图
renderer.AddActor(actor)
renderer.ResetCamera()
render_windows.append(render_window)
# -----------------------------------------------------------------------------
# Trame用户界面
# -----------------------------------------------------------------------------
with SinglePageLayout(server) as layout:
layout.icon.click = ctrl.reset_camera
layout.title.set_text("并排shp视图对比")
with layout.toolbar:
vuetify.VSpacer()
vuetify.VDivider(vertical=True, classes="mx-2")
with vuetify.VBtn(icon=True, click=ctrl.reset_all_views):
vuetify.VIcon("mdi-camera-reset")
vuetify.VDivider(vertical=True, classes="mx-2")
with vuetify.VBtn(icon=True, click=ctrl.update_all_views):
vuetify.VIcon("mdi-refresh")
vuetify.VDivider(vertical=True, classes="mx-2")
vuetify.VSelect(
label="视图布局",
items=[
{"text": "水平布局", "value": "horizontal"},
{"text": "垂直布局", "value": "vertical"},
],
v_model=("layout_mode",),
dense=True,
hide_details=True,
style="max-width: 120px;",
)
with layout.content:
# 使用条件渲染实现布局切换
with vuetify.VContainer(fluid=True, classes="pa-0 fill-height"):
# 水平布局
with vuetify.VRow(v_if="layout_mode === 'horizontal'", classes="pa-0 ma-0 fill-height"):
with vuetify.VCol(classes="pa-1 ma-0 fill-height"):
view1 = vtk_widgets.VtkRemoteView(
render_windows[0],
ref="view1",
interactive_ratio=1,
style="height: 100%;",
)
html_views.append(view1)
with vuetify.VCol(classes="pa-1 ma-0 fill-height"):
view2 = vtk_widgets.VtkRemoteView(
render_windows[1],
ref="view2",
interactive_ratio=1,
style="height: 100%;",
)
html_views.append(view2)
# 垂直布局
with vuetify.VCol(v_if="layout_mode === 'vertical'", classes="pa-0 ma-0 fill-height"):
with vuetify.VRow(classes="pa-1 ma-0", style="height: 50%;"):
view1 = vtk_widgets.VtkRemoteView(
render_windows[0],
ref="view1",
interactive_ratio=1,
style="height: 100%;",
)
html_views.append(view1)
with vuetify.VRow(classes="pa-1 ma-0", style="height: 50%;"):
view2 = vtk_widgets.VtkRemoteView(
render_windows[1],
ref="view2",
interactive_ratio=1,
style="height: 100%;",
)
html_views.append(view2)
# 定义控制函数
def reset_all_views():
for view in html_views:
view.reset_camera()
def update_all_views():
for view in html_views:
view.update()
ctrl.reset_all_views = reset_all_views
ctrl.update_all_views = update_all_views
# -----------------------------------------------------------------------------
# 启动应用
# -----------------------------------------------------------------------------
if __name__ == "__main__":
server.start(port=1234)