65.9K
CodeProject 正在变化。 阅读更多。
Home

SQL Geometry 查看器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (16投票s)

2008 年 9 月 8 日

CPOL

5分钟阅读

viewsIcon

98281

downloadIcon

5130

SQL Geometry 类型的屏幕可视化工具。

引言

SQL Server 2008 包含两种新的数据类型:geographygeometry。这些数据类型的表列存储的数据与 Open Geospatial Consortium 的标准兼容。然而,SQL Server 附带的工具并未提供可视化数据的方法。这款小工具应运而生,它正是为此 geometry 类型而设计的。

该工具的功能

该工具支持两种输入模式:用户输入和数据库输入。在这两种输入模式下,给定的几何图形都会被添加到网格中以进行表格视图显示,并添加到画布以进行可视化。

用户输入

用户输入模式下,我们可以输入几何图形的 WKT(Well-Known Text)描述,例如 POLYGON((0 0, 50 0, 50 50, 0 50, 0 0)),它描述了一个以坐标轴原点为左下角的 50x50 的正方形。

数据库输入

数据库输入模式下,在连接到 SQL Server 2008 数据库后,我们可以输入一个返回最多两列数据的 SQL 查询。如果查询返回两列数据,第一列用于标识几何图形,第二列是几何图形本身。如果查询返回一列数据,该列本身就是几何图形,并由自动生成的 GUID 来标识。

背景

这个小工具的灵感来源于 Simon Sabin 的 SpatialViewer。事实上,Simon 的一部分代码被直接复制了过来。SpatialViewer 提供了从用户在画布上用鼠标绘制的几何图形生成 WKT 并展示给用户的功能。然而,它存在两个限制:

  • 它无法接受来自数据库的输入(但这一点很容易纠正);以及
  • 它是用 Windows Forms 编写的,因此未能充分利用 WPF 出色的数据绑定和绘图能力。

代码解析

主要关注的文件是 GeometryInfo.cs,它包含两个类:GeometryInfoGeometryCollection。前者封装了一个几何图形,并提供了返回几何图形面积、长度以及是否为有效几何图形的属性。几何图形本身存储在 Data 属性中,该属性的类型为 SqlGeometry

只读属性 Geometry 返回 WPF Geometry 类的实例。为了实现这一点,Decode() 方法解析几何图形的 WKT 并返回相应的 Geometry 实例。

private Geometry Decode(SqlGeometry g)
{
    PathGeometry result = new PathGeometry();

    switch (g.STGeometryType().Value.ToLower())
    {
        case "point":
            PathFigure pointFig = new PathFigure();

            pointFig.StartPoint = new Point(g.STX.Value - 2, g.STY.Value - 2);
            LineSegment pointLs = new LineSegment(new Point(g.STX.Value + 2, 
                                      g.STY.Value + 2), true);
            pointFig.Segments.Add(pointLs);
            result.Figures.Add(pointFig);

            pointFig = new PathFigure();
            pointFig.StartPoint = new Point(g.STX.Value - 2, g.STY.Value + 2);
            pointLs = new LineSegment(new Point(g.STX.Value + 2, 
                                      g.STY.Value - 2), true);
            pointFig.Segments.Add(pointLs);
            result.Figures.Add(pointFig);

            return result;
        case "polygon":
            string cmd = new string(g.STAsText().Value).Trim().Substring(8);
            string[] polyArray = (cmd.Substring(1, cmd.Length - 2) + 
                                  ", ").Split('(');
            var polys = from s in polyArray
                        where s.Length > 0
                        select s.Trim().Substring(0, s.Length - 3);

            PathFigure fig;
            foreach (var item in polys)
            {
                fig = new PathFigure();
                var polyPoints = from p in item.Split(',')
                                 select p.Trim().Replace(" ", ",");
                fig.StartPoint = Point.Parse(polyPoints.ElementAt(0));
                for (int i = 1; i < polyPoints.Count(); i++)
                {
                    LineSegment ls = new LineSegment(
                                Point.Parse(polyPoints.ElementAt(i)), true);
                    fig.Segments.Add(ls);
                }
                result.Figures.Add(fig);
            }

            return result;
        case "linestring":
            PathFigure lsfig = new PathFigure();
            lsfig.StartPoint = new Point(g.STPointN(1).STX.Value, 
                                         g.STPointN(1).STY.Value);
            for (int i = 1; i <= g.STNumPoints(); i++)
            {
                LineSegment ls = new LineSegment();
                ls.Point = new Point(g.STPointN(i).STX.Value, 
                                     g.STPointN(i).STY.Value);
                lsfig.Segments.Add(ls);
            }
            result.Figures.Add(lsfig);

            return result;
        case "multipoint":
        case "multilinestring":
        case "multipolygon":
        case "geometrycollection":
            GeometryGroup mpG = new GeometryGroup();
            for (int i = 1; i <= g.STNumGeometries().Value; i++)
                mpG.Children.Add(Decode(g.STGeometryN(i)));

            return mpG;
        default:
            return Geometry.Empty;
    }
}

Decode() 方法使用 SqlGeometry 类的 STGeometryType() 方法查询几何图形的类型。如果该几何图形是一个几何集合(如 MultiPointMultiLineStringMultiPolygonGeometryCollection),它会创建一个 GeometryGroup,并通过递归调用 Decode() 来处理集合中的每个子项,并将它们添加到 GeometryGroup 中。否则,它会创建一个 PathGeometry 来表示给定的 SqlGeometry。请注意,点以十字架的形式添加,包围盒边长为 4。

GeometryCollection 类继承自 DependencyObject,提供了三个只读的依赖属性:

  • TranslateXTranslateY,它们表示几何集合需要平移的偏移量,以便将其左上角放置在坐标轴原点;以及
  • BoundingBox,它返回几何集合的包围盒——包含整个几何图形的最小矩形。

该类还公开了第四个属性 Geometries,类型为 ObservableCollection<GeometryInfo>。当底层集合发生变化时,其他三个属性会得到更新。这是通过将事件处理程序附加到集合的 CollectionChanged 事件来实现的。

void GeometriesCollectionChanged(object sender, 
     System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
    if (Geometries.Count > 0)
    {
        var minX = (from p in Geometries
                    select p.Data.STEnvelope().STPointN(1).STX.Value).Min();
        var maxX = (from p in Geometries
                    select p.Data.STEnvelope().STPointN(2).STX.Value).Max();
        var minY = (from p in Geometries
                    select p.Data.STEnvelope().STPointN(1).STY.Value).Min();
        var maxY = (from p in Geometries
                    select p.Data.STEnvelope().STPointN(4).STY.Value).Max();
        SetValue(BoundingBoxPropertyKey, new Rect(minX, minY, maxX - minX, maxY - minY));
        SetValue(TranslateXPropertyKey, -BoundingBox.TopLeft.X);
        SetValue(TranslateYPropertyKey, -BoundingBox.TopLeft.Y);
    }
    else
    {
        SetValue(BoundingBoxPropertyKey, new Rect(0, 0, 0, 0));
        SetValue(TranslateXPropertyKey, 0.0);
        SetValue(TranslateYPropertyKey, 0.0);
    }
}

使用工具

使用主窗口底部的选项卡页可以在用户输入模式和数据库输入模式之间切换。

在用户输入模式下,每行输入一个几何命令。这些命令会在您输入时被解析,解析结果会显示在命令下方。如果几何图形有效,请单击“添加”按钮将其添加到查看器。如果无效,但可以从中构建一个有效的几何图形,请单击“添加有效”按钮。

在数据库输入模式下,首先连接到一个包含空间数据表的 SQL Server 2008 数据库(连接状态显示在状态栏上)。然后,键入一个 SQL 命令(遵循简介中的指导方针),最后单击“添加”按钮。

请注意,添加几何图形不会删除已添加的几何图形。要删除几何图形,请从网格中选择它并按“删除”按钮。要完全清除几何图形列表,请单击工具栏上的“清空画布”按钮。

使用工具栏上的滑块可以放大或缩小。缩放系数的范围从 0.1 到 30。

单击并拖动画布进行平移。

关注点

实现画布和实现平移是两个有趣的挑战。解决一个问题竟然也解决了另一个问题。画布由一个 Border 组成,其中包含一个 Canvas,该 Canvas 又包含一个 ItemsControlItemsControl 绑定到 GeometryCollection,并且其样式设置为 ItemsPanel 包含一个 Grid(用于绝对定位),并且每个项都呈现为一个 Path,其几何图形绑定到该项的 Geometry 属性。

<Style TargetType="ItemsControl">
    <Setter Property="ItemsPanel">
        <Setter.Value>
            <ItemsPanelTemplate>
                <Grid>
                    <Grid.RenderTransform>
                        <TransformGroup>
                            <TranslateTransform 
                                X="{Binding Path=TranslateX}" 
                                Y="{Binding Path=TranslateY}" />
                        </TransformGroup>
                    </Grid.RenderTransform>
                    <Grid.LayoutTransform>
                        <TransformGroup>
                            <ScaleTransform 
                                CenterX="0" CenterY="0" 
                                ScaleX="{Binding ElementName=sldZoom, 
                                       Path=Value}" 
                                ScaleY="{Binding ElementName=sldZoom, 
                                       Path=Value}" />
                        </TransformGroup>
                    </Grid.LayoutTransform>
                </Grid>
            </ItemsPanelTemplate>
        </Setter.Value>
    </Setter>
</Style>

<DataTemplate DataType="{x:Type local:GeometryInfo}">
    <Path Data="{Binding Path=Geometry}" 
          Stroke="Black" StrokeThickness="0.2" 
          Fill="{Binding Path=Fill}">
        <Path.ToolTip>
            <StackPanel Width="250" TextBlock.FontSize="12">
                <TextBlock FontWeight="Bold" Text="
                     {Binding Path=Id}" />
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="Area: " />
                    <TextBlock Text="{Binding Path=Area}" />
                    <TextBlock Text=" units" />
                </StackPanel>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="Length: " />
                    <TextBlock Text="{Binding Path=Length}" />
                    <TextBlock Text=" units" />
                </StackPanel>
            </StackPanel>
        </Path.ToolTip>
    </Path>
</DataTemplate>

最后,Border 处理 PreviewMouseUpPreviewMouseDownPreviewMouseMove 事件,以便应用适当的 TranslateTransformItemsControl 来实现平移。请注意,将 MatrixTransformTranslateTransform 应用于 Border 以避免画布颠倒显示(在笛卡尔坐标系中,Y 坐标随着向上移动而增加,而在 WPF 坐标系中,Y 坐标随着向屏幕底部移动而增加)。

<Border Margin="4,0,4,4" BorderThickness="0.5,0.5,0.5,0.5" 
        BorderBrush="{DynamicResource 
                    {x:Static SystemColors.ActiveCaptionTextBrushKey}}" 
        Background="Transparent" Name="masterCanvas" 
        PreviewMouseMove="masterCanvas_PreviewMouseMove" 
        PreviewMouseDown="masterCanvas_PreviewMouseDown" 
        PreviewMouseUp="masterCanvas_PreviewMouseUp">
    <Border.RenderTransform>
        <TransformGroup>
            <MatrixTransform Matrix="1,0,0,-1,0,0" />
            <TranslateTransform 
                X="0" 
                Y="{Binding ElementName=masterCanvas, Path=ActualHeight}" />
        </TransformGroup>
    </Border.RenderTransform>
    <Canvas ClipToBounds="True">
        <ItemsControl Name="drawingCanvas" 
                  IsTabStop="False" 
                  ItemsSource="{Binding Path=Geometries}">
            <ItemsControl.RenderTransform>
                <TranslateTransform X="{Binding Path=BoundingBox.X}"
                                    Y="{Binding Path=BoundingBox.Y}" />
            </ItemsControl.RenderTransform>
        </ItemsControl>
    </Canvas>
</Border>

历史

  • 2008-9-8:发布第一个版本
  • 2008-9-9:更新了演示
© . All rights reserved.