在 UnionAggregate 中保留起点
Preserve start points in UnionAggregate
用例 1:
DECLARE @Geom TABLE
(
shape geometry,
shapeType nvarchar(50)
);
INSERT INTO @Geom(shape,shapeType)
VALUES('LINESTRING(1 2, 3 4)', 'A'),
('LINESTRING(3.2 4, 7 8)', 'B');
SELECT *
FROM @Geom
SELECT geometry::UnionAggregate(shape).ToString(), geometry::UnionAggregate(shape)
FROM @Geom;
输出的 WKT 是
MULTILINESTRING ((7 8, 3.2 4), (3 4, 1 2))
什么时候我想要
MULTILINESTRING ((1 2, 3 4), (3.2 4, 7 8))
其中 "A" 和 "B" 行的开头应该是 (1 2)
和 (3.2 4)
。
这种UnionAggregate
的行为似乎并不关心"direction"的几何形状,以保持A union B和B union A是相同的结果。但是,我想保留 start/endpoints,因为我正在合并街道几何图形,并且我希望所有 LINESTRING 都沿其原始方向移动。
他们似乎建议了一个关于检查最终结果的可能解决方案,但我不清楚该怎么做。在链接的线程中暗示
The MultiLineString always represents the graph from the point which farthest from the origin point.
我不清楚这到底是什么意思,但我认为我不能假设 UnionAggregate 的结果总是与我想要的相反
如果很难知道方向意图,那么我可以添加 M 度量,其中方向应遵循增加的 M 值。
假设我有一种方法可以反转直线上的点,我将如何解决这个问题?
我发现了一个模仿 STUnion
的函数,以增加对 Z 和 M 度量的支持:http://www.spatialdbadvisor.com/files/SQLServer.html#robo48 但是注意到 "their direction could change (eg Start/Start Point relationship).",这是我想避免的。
编辑:
我还需要的功能是当 LINESTRING 有一个共享端点时,结果是连接 LINESTRING
用例 2:
DECLARE @Geom TABLE
(
shape geometry,
shapeType nvarchar(50)
);
INSERT INTO @Geom(shape,shapeType)
VALUES('LINESTRING(1 2, 3 4)', 'A'),
('LINESTRING(3 4, 7 8)', 'B');
SELECT *
FROM @Geom
SELECT geometry::UnionAggregate(shape).ToString(), geometry::UnionAggregate(shape)
FROM @Geom;
这导致 WKT LINESTRING (7 8, 3 4, 1 2)
什么时候我会想要
LINESTRING (1 2, 3 4, 7 8)
尝试解决
Clay 建议的 geometry::CollectionAggregate(shape).Reduce(0)
解决了用例 1。我尝试只在结果上使用 STUnion 和一个空的线串,当它工作时它会退回到不正确的顺序。
我怀疑解决方案将是一个类似于 ST_LineMerge 的缩放函数,它获取 CollectionAggregate (MULTILINESTRING) 的结果,然后在可以合并为一个 LINESTRING 和不能合并时将这些点合并在一起returns 几何形状不变
几何类型没有 record/encode 方向性。您给它的行可能被认为是 "undirected" 或 "bi-directional"。这个returns1:
select geometry::STGeomFromText('LINESTRING(1 2, 3 4)',0).STEquals(
geometry::STGeomFromText('LINESTRING(3 4, 1 2)',0))
因此,使用这些类型无法获得您正在寻找的内容。您认为 "start points" 很特别。我建议您将它们分别记录为个人 POINT
s.
不过,这确实使所有生成的代码现在变得更难看 - 您必须将这些数据对保持在一起处理:
DECLARE @Geom TABLE
(
start geometry,
shape geometry,
shapeType nvarchar(50)
);
INSERT INTO @Geom(start,shape,shapeType)
VALUES('POINT(1 2)','LINESTRING(1 2, 3 4)', 'A'),
('POINT(3.2 4)','LINESTRING(3.2 4, 7 8)', 'B');
SELECT *
FROM @Geom
SELECT
geometry::UnionAggregate(start).ToString(), geometry::UnionAggregate(shape).ToString(),
geometry::UnionAggregate(start), geometry::UnionAggregate(shape)
FROM @Geom;
此时您可以决定停止直接使用地理类型 - 您可以创建一个引用 SqlGeography
的 CLR UDT(相同类型的 CLR 表面处理) 并在内部使用它,但也跟踪它的 "directionality",全部包裹在一起,然后开始使用它。
不过,您不太可能希望在该包装器中显示所有 geography
方法 - 您将不得不挑选并选择您的战斗。而且,当然,由于它并不是真正的 SQL 服务器 geography
出现在您的结果中,您将无法从 Management Studio 中的 "Spatial Results" 选项卡中受益。
在这些类型中,我唯一能想到的 "directionality" 确实存在的地方是用于消除 geography
形状歧义的左手法则。
本来我建议...
DECLARE @Geom TABLE
(
shape geometry,
shapeType nvarchar(50)
);
INSERT @Geom(shape,shapeType) VALUES
('LINESTRING(1 2, 3 4)', 'A'),
('LINESTRING(3.2 4, 7 8)', 'B');
SELECT * FROM @Geom
SELECT
geometry::CollectionAggregate(shape).Reduce(0).ToString(),
geometry::CollectionAggregate(shape).Reduce(0)
FROM @Geom
你得到:
...但是,我很清楚我给出的答案还不够好。例如,很难让 Reduce()
不简化你的部分台词,
我仍然喜欢 CollectionAggregate,因为它可以将您的原始线条数组合并为一个东西,但后来我认为必须有一种方法来构建必要的几何结构。
我玩了几次,这次迭代将评估为 LineString
或 MultiLineString
,具体取决于输入中是否有不相交的 LineString
元素:
create function dbo.SimplifyToLine( @geo geometry ) returns geometry as
begin
declare
@numSubGeos int = @geo.STNumGeometries(),
@subGeoIdx int = 1,
@sql nvarchar( max ) = N'',
@subGeo geometry,
@oldEndX float = -1.0e26,
@oldEndY float = -1.0e26,
@startX float,
@startY float,
@endX float,
@endY float,
@idx int,
@numPoints int,
@point geometry,
@segment int = 1,
@continue bit,
@result geometry,
@started bit = 0
declare
@geos table
(
Idx int primary key,
SubGeo geometry,
StartX decimal,
EndX decimal,
StartY decimal,
EndY decimal,
NumPoints int,
ContinueFromPrevious bit
)
declare
@multiLines table
(
Idx int primary key,
Segment nvarchar(max)
)
--> collect geometries and extents...
while ( @subGeoIdx <= @numSubGeos )
begin
select @subGeo = @geo.STGeometryN( @subGeoIdx )
select
@startX = @subGeo.STPointN( 1 ).STX,
@startY = @subGeo.STPointN( 1 ).STY,
@endX = @subGeo.STPointN( @subGeo.STNumPoints( ) ).STX,
@endY = @subGeo.STPointN( @subGeo.STNumPoints( ) ).STY
insert @geos values
(
@subGeoIdx,
@subGeo,
@startX,
@endX,
@startY,
@endY,
@subGeo.STNumPoints() ,
case when @subGeoIdx = 1 then 1 when @oldEndX = @startX and @oldEndY = @startY then 1 else 0 end
)
select
@oldEndX = @endX,
@oldEndY = @endY,
@subGeoIdx = @subGeoIdx + 1
end
if not exists ( select * from @geos where ContinueFromPrevious = 0 ) --> then all LineStrings are connected
begin
--> build a single LINESTRING( )...
select @sql = ''
declare c cursor for select SubGeo, StartX, EndX, StartY, EndY, NumPoints, ContinueFromPrevious from @geos order by Idx
open c
while ( 1 = 1 )
begin
fetch next from c into @subGeo, @startX, @endX, @startY, @endY, @numPoints, @continue
if @@fetch_status != 0 break;
select @idx = case when @started = 0 then 1 else 2 end, @started = 1 --> accrue all points, de-duplicating line ends...
while ( @idx <= @numPoints )
begin
select @point = @subGeo.STPointN( @idx )
select @sql += convert( nvarchar, @point.STX ) + N' ' + convert( nvarchar, @point.STY ) + N','
select @idx = @idx + 1
end
end
close c
deallocate c
select @sql = substring( @sql, 1, len( @sql ) -1 )
select @result = geometry::STGeomFromText(N'LINESTRING(' + @sql + N')', 0 )
end
else --> we have disjoint lines in the inputs...
begin
select @sql = N'', @started = 0
--> build a MULTILINESTRING((),()...) with line segements terminated at disjoint points..
declare c cursor for select SubGeo, StartX, EndX, StartY, EndY, NumPoints, ContinueFromPrevious from @geos order by Idx
open c
while ( 1=1 )
begin
fetch next from c into @subGeo, @startX, @endX, @startY, @endY, @numPoints, @continue
if @@fetch_status != 0 break;
if @continue = 1
begin
select @idx = case when @started = 0 then 1 else 2 end, @started = 1
while ( @idx <= @numPoints )
begin
select @point = @subGeo.STPointN( @idx )
select @sql += convert( nvarchar, @point.STX ) + N' ' + convert( nvarchar, @point.STY ) + N','
select @idx = @idx + 1
end
end
else
begin
insert @multiLines values ( @segment, substring( @sql, 1, len( @sql ) -1 ) ) --> collect the segment
select @idx = 1, @sql = N'', @segment = @segment + 1
while ( @idx <= @numPoints )
begin
select @point = @subGeo.STPointN( @idx )
select @sql += convert( nvarchar, @point.STX ) + N' ' + convert( nvarchar, @point.STY ) + N','
select @idx = @idx + 1
end
end
end
close c
deallocate c
insert @multiLines values ( @segment, substring( @sql, 1, len( @sql ) -1 ) )
select @sql = N''
select @sql += N'(' + Segment + N'),' from @multiLines order by Idx --> appends all segments
select @sql = substring( @sql, 1, len( @sql ) -1 )
select @result = geometry::STGeomFromText( 'MULTILINESTRING('+ @sql + N')', 1 )
end
...最后给出:
DECLARE @Geom TABLE
(
shape geometry,
shapeType nvarchar(50)
);
INSERT @Geom(shape,shapeType) VALUES
('LINESTRING(1 2, 3 4)', 'A'),
('LINESTRING(3 4, 9 9)', 'B'), --> disjoint from here to the next LINESTRING
('LINESTRING(9 8, 3 4)', 'C'),
('LINESTRING(3 4, 1 2)', 'D');
select
dbo.SimplifyToLine(geometry::CollectionAggregate(shape)).ToString(),
dbo.SimplifyToLine(geometry::CollectionAggregate(shape))
from
@Geom
delete @Geom
INSERT @Geom(shape,shapeType) VALUES
('LINESTRING(1 2, 3 4)', 'A'),
('LINESTRING(3 4, 9 8)', 'B'),
('LINESTRING(9 8, 3 4)', 'C'),
('LINESTRING(3 4, 1 2)', 'D');
select
dbo.SimplifyToLine(geometry::CollectionAggregate(shape)).ToString(),
dbo.SimplifyToLine(geometry::CollectionAggregate(shape))
from
@Geom
...你得到:
与 Clay 传递 GeometryCollection 的想法不同,我实现了一个强大的版本,它将采用 POINT、MULTIPOINT、LINESTRING、MULTILINESTRING 的任意组合,并删除 @Tolerance
中的任何接触端点并创建一个 POINT、LINESTRING , 多行
这是它的工作演示(注意 0 和 0.1 的公差如何影响第二和第三个输出):
DECLARE @GeometryCollection GEOMETRY = GEOMETRY::STGeomFromText('GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4), LINESTRING (3 4, 100 100), LINESTRING (9 8, 3 4), LINESTRING (3 4, 1 2), POINT(1 2), POINT(1 2), POINT(1 2))',0)
SELECT [dbo].[fnSimplifyToLine](@GeometryCollection, 0).ToString();
--Output: MULTILINESTRING ((1 2, 3 4, 100 100), (9 8, 3 4, 1 2))
SET @GeometryCollection = GEOMETRY::STGeomFromText('GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4.1), LINESTRING (3 4, 9 9, 6 1))',0)
SELECT [dbo].[fnSimplifyToLine](@GeometryCollection, 0).ToString()
--Output: MULTILINESTRING ((1 2, 3 4.1), (3 4, 9 9, 6 1))
SET @GeometryCollection = GEOMETRY::STGeomFromText('GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4.1), LINESTRING (3 4, 9 9, 6 1))',0)
SELECT [dbo].[fnSimplifyToLine](@GeometryCollection, 0.1).ToString()
--Output: LINESTRING (1 2, 3 4.1, 9 9, 6 1)
SET @GeometryCollection = GEOMETRY::STGeomFromText('GEOMETRYCOLLECTION (POINT(1 2))',0)
SELECT [dbo].[fnSimplifyToLine](@GeometryCollection, 0).ToString()
--Output: POINT (1 2)
SET @GeometryCollection = GEOMETRY::STGeomFromText('GEOMETRYCOLLECTION (MULTIPOINT((1 2), (2 3)))',0)
SELECT [dbo].[fnSimplifyToLine](@GeometryCollection, 0).ToString()
--Output: (1 2, 2 3)
首先,我必须创建一个递归 CTE 函数,它采用几何并提取所有点。
CREATE FUNCTION [dbo].[fnGetPoints]
(
@Geometry GEOMETRY
)
RETURNS TABLE
AS
RETURN
(
WITH GeometryPoints(N, Point) AS (
SELECT
CAST(1 AS DECIMAL(9,2)) as N
,@Geometry.STPointN(1) as Point
UNION ALL
SELECT
CAST(N + 1.0 AS DECIMAL(9,2)) as N
,@Geometry.STPointN(N + 1) as Point
FROM GeometryPoints GP
WHERE N < @Geometry.STNumPoints()
)
SELECT *
FROM GeometryPoints
)
然后我创建了一个函数,将 fnGetPoints
交叉应用到 @GeometryCollection
中的每个几何体以获得点矩阵。使用窗口函数 (LAG) 查找端点在 @Tolerance
内的位置并删除这些点。然后我做了一个 data smear 来组合它们共享端点的几何图形。
CREATE FUNCTION [dbo].[fnSimplifyToLine] (@GeometryCollection GEOMETRY, @Tolerance DECIMAL(19,10))
RETURNS GEOMETRY
AS
BEGIN
DECLARE @PointMatrix TABLE (
PointId INT,
LinestringId INT,
GeometryIndex INT,
GeometryType varchar(100),
PointIndex INT,
Point GEOMETRY,
Duplicate BIT
);
DECLARE @Linestrings TABLE (
LinestringId INT,
PointArrayStr varchar(max)
);
WITH CollectionGeometries(N, Geom) AS (
SELECT
CAST(1 AS DECIMAL(9,2)) as N
,@GeometryCollection.STGeometryN(1) as Geom
UNION ALL
SELECT
CAST(N + 1.0 AS DECIMAL(9,2)) as N
, @GeometryCollection.STGeometryN(N + 1) as Geom
FROM CollectionGeometries CG
WHERE N < @GeometryCollection.STNumGeometries()
), PointMatrix AS (
SELECT
ROW_NUMBER() OVER(ORDER BY G.N, P.N) as PointId
,G.N as GeometryIndex
,G.Geom.STGeometryType() as GeometryType
,P.N as PointIndex
,P.Point
FROM CollectionGeometries G
CROSS APPLY dbo.fnGetPoints(Geom) P
)
INSERT INTO @PointMatrix
SELECT
PointId
,GeometryIndex as LinestringId
,GeometryIndex
,GeometryType
,PointIndex
,Point
,CASE
WHEN
GeometryIndex != LAG(GeometryIndex) OVER(ORDER BY PointId)
AND ABS(Point.STX - LAG(Point.STX) OVER(ORDER BY PointId)) <= @Tolerance
AND ABS(Point.STY - LAG(Point.STY) OVER(ORDER BY PointId)) <= @Tolerance
THEN 1
ELSE 0
END as Duplicate
FROM PointMatrix
OPTION (MAXRECURSION 10000)
-- POLYGON, MULTIPOLYGON, GEOMETRYCOLLECTION, CIRCULARSTRING, COMPOUNDCURVE, CURVEPOLYGON not supported
IF EXISTS ( SELECT * FROM @PointMatrix WHERE GeometryType NOT IN ('POINT', 'MULTIPOINT', 'LINESTRING', 'MULTILINESTRING'))
RETURN CAST('Geometries in @GeometryCollection must all be IN (''POINT'',''MULTIPOINT'', ''LINESTRING'', ''MULTILINESTRING'')' as GEOMETRY);
DECLARE @SRID INT = (SELECT DISTINCT Point.STSrid FROM @PointMatrix)
UPDATE @PointMatrix
SET LinestringId = NULL
WHERE GeometryIndex IN (
SELECT GeometryIndex FROM @PointMatrix WHERE Duplicate = 1
)
DELETE @PointMatrix
WHERE Duplicate = 1;
-- Data smear
WITH Cnt AS (
SELECT PointId, Point, LinestringId,c=COUNT(LinestringId) OVER (ORDER BY PointId)
FROM @PointMatrix
), SmearedLineStringId AS (
SELECT PointId, Point, LinestringId=MAX(LinestringId) OVER (PARTITION BY c)
FROM Cnt
)
INSERT @Linestrings
SELECT
LinestringId
,'(' +
STUFF((
SELECT ',' + CAST(Point.STX as varchar(100)) + ' ' + CAST(Point.STY as varchar(100))
FROM SmearedLineStringId t2
WHERE t1.LinestringId = t2.LinestringId
ORDER BY PointId
FOR XML PATH ('')
), 1, 1, '')
+ ')' as PointArray
FROM SmearedLineStringId t1
GROUP BY LinestringId
DECLARE @Type varchar(100) = CASE
WHEN 1 =(SELECT COUNT(*) FROM @PointMatrix) THEN
'POINT'
WHEN 1 =(SELECT COUNT(*) FROM @Linestrings) THEN
'LINESTRING'
ELSE
'MULTILINESTRING'
END
DECLARE @BeginParens char(1) = '(';
DECLARE @EndParens char(1) = ')'
IF @Type != 'MULTILINESTRING'
BEGIN
SET @BeginParens = '';
SET @EndParens = '';
END
DECLARE @Wkt varchar(max) = @Type + @BeginParens +
STUFF((
SELECT ',' + PointArrayStr
FROM @Linestrings t2
ORDER BY LinestringId
FOR XML PATH ('')
), 1, 1, '')
+ @EndParens
RETURN Geometry::STGeomFromText(@Wkt, @SRID)
END
GO
用例 1:
DECLARE @Geom TABLE
(
shape geometry,
shapeType nvarchar(50)
);
INSERT INTO @Geom(shape,shapeType)
VALUES('LINESTRING(1 2, 3 4)', 'A'),
('LINESTRING(3.2 4, 7 8)', 'B');
SELECT *
FROM @Geom
SELECT geometry::UnionAggregate(shape).ToString(), geometry::UnionAggregate(shape)
FROM @Geom;
输出的 WKT 是
MULTILINESTRING ((7 8, 3.2 4), (3 4, 1 2))
什么时候我想要
MULTILINESTRING ((1 2, 3 4), (3.2 4, 7 8))
其中 "A" 和 "B" 行的开头应该是 (1 2)
和 (3.2 4)
。
这种UnionAggregate
的行为似乎并不关心"direction"的几何形状,以保持A union B和B union A是相同的结果。但是,我想保留 start/endpoints,因为我正在合并街道几何图形,并且我希望所有 LINESTRING 都沿其原始方向移动。
他们似乎建议了一个关于检查最终结果的可能解决方案,但我不清楚该怎么做。在链接的线程中暗示
The MultiLineString always represents the graph from the point which farthest from the origin point.
我不清楚这到底是什么意思,但我认为我不能假设 UnionAggregate 的结果总是与我想要的相反
如果很难知道方向意图,那么我可以添加 M 度量,其中方向应遵循增加的 M 值。
假设我有一种方法可以反转直线上的点,我将如何解决这个问题?
我发现了一个模仿 STUnion
的函数,以增加对 Z 和 M 度量的支持:http://www.spatialdbadvisor.com/files/SQLServer.html#robo48 但是注意到 "their direction could change (eg Start/Start Point relationship).",这是我想避免的。
编辑:
我还需要的功能是当 LINESTRING 有一个共享端点时,结果是连接 LINESTRING
用例 2:
DECLARE @Geom TABLE
(
shape geometry,
shapeType nvarchar(50)
);
INSERT INTO @Geom(shape,shapeType)
VALUES('LINESTRING(1 2, 3 4)', 'A'),
('LINESTRING(3 4, 7 8)', 'B');
SELECT *
FROM @Geom
SELECT geometry::UnionAggregate(shape).ToString(), geometry::UnionAggregate(shape)
FROM @Geom;
这导致 WKT LINESTRING (7 8, 3 4, 1 2)
什么时候我会想要
LINESTRING (1 2, 3 4, 7 8)
尝试解决
Clay 建议的 geometry::CollectionAggregate(shape).Reduce(0)
解决了用例 1。我尝试只在结果上使用 STUnion 和一个空的线串,当它工作时它会退回到不正确的顺序。
我怀疑解决方案将是一个类似于 ST_LineMerge 的缩放函数,它获取 CollectionAggregate (MULTILINESTRING) 的结果,然后在可以合并为一个 LINESTRING 和不能合并时将这些点合并在一起returns 几何形状不变
几何类型没有 record/encode 方向性。您给它的行可能被认为是 "undirected" 或 "bi-directional"。这个returns1:
select geometry::STGeomFromText('LINESTRING(1 2, 3 4)',0).STEquals(
geometry::STGeomFromText('LINESTRING(3 4, 1 2)',0))
因此,使用这些类型无法获得您正在寻找的内容。您认为 "start points" 很特别。我建议您将它们分别记录为个人 POINT
s.
不过,这确实使所有生成的代码现在变得更难看 - 您必须将这些数据对保持在一起处理:
DECLARE @Geom TABLE
(
start geometry,
shape geometry,
shapeType nvarchar(50)
);
INSERT INTO @Geom(start,shape,shapeType)
VALUES('POINT(1 2)','LINESTRING(1 2, 3 4)', 'A'),
('POINT(3.2 4)','LINESTRING(3.2 4, 7 8)', 'B');
SELECT *
FROM @Geom
SELECT
geometry::UnionAggregate(start).ToString(), geometry::UnionAggregate(shape).ToString(),
geometry::UnionAggregate(start), geometry::UnionAggregate(shape)
FROM @Geom;
此时您可以决定停止直接使用地理类型 - 您可以创建一个引用 SqlGeography
的 CLR UDT(相同类型的 CLR 表面处理) 并在内部使用它,但也跟踪它的 "directionality",全部包裹在一起,然后开始使用它。
不过,您不太可能希望在该包装器中显示所有 geography
方法 - 您将不得不挑选并选择您的战斗。而且,当然,由于它并不是真正的 SQL 服务器 geography
出现在您的结果中,您将无法从 Management Studio 中的 "Spatial Results" 选项卡中受益。
在这些类型中,我唯一能想到的 "directionality" 确实存在的地方是用于消除 geography
形状歧义的左手法则。
本来我建议...
DECLARE @Geom TABLE
(
shape geometry,
shapeType nvarchar(50)
);
INSERT @Geom(shape,shapeType) VALUES
('LINESTRING(1 2, 3 4)', 'A'),
('LINESTRING(3.2 4, 7 8)', 'B');
SELECT * FROM @Geom
SELECT
geometry::CollectionAggregate(shape).Reduce(0).ToString(),
geometry::CollectionAggregate(shape).Reduce(0)
FROM @Geom
你得到:
...但是,我很清楚我给出的答案还不够好。例如,很难让 Reduce()
不简化你的部分台词,
我仍然喜欢 CollectionAggregate,因为它可以将您的原始线条数组合并为一个东西,但后来我认为必须有一种方法来构建必要的几何结构。
我玩了几次,这次迭代将评估为 LineString
或 MultiLineString
,具体取决于输入中是否有不相交的 LineString
元素:
create function dbo.SimplifyToLine( @geo geometry ) returns geometry as
begin
declare
@numSubGeos int = @geo.STNumGeometries(),
@subGeoIdx int = 1,
@sql nvarchar( max ) = N'',
@subGeo geometry,
@oldEndX float = -1.0e26,
@oldEndY float = -1.0e26,
@startX float,
@startY float,
@endX float,
@endY float,
@idx int,
@numPoints int,
@point geometry,
@segment int = 1,
@continue bit,
@result geometry,
@started bit = 0
declare
@geos table
(
Idx int primary key,
SubGeo geometry,
StartX decimal,
EndX decimal,
StartY decimal,
EndY decimal,
NumPoints int,
ContinueFromPrevious bit
)
declare
@multiLines table
(
Idx int primary key,
Segment nvarchar(max)
)
--> collect geometries and extents...
while ( @subGeoIdx <= @numSubGeos )
begin
select @subGeo = @geo.STGeometryN( @subGeoIdx )
select
@startX = @subGeo.STPointN( 1 ).STX,
@startY = @subGeo.STPointN( 1 ).STY,
@endX = @subGeo.STPointN( @subGeo.STNumPoints( ) ).STX,
@endY = @subGeo.STPointN( @subGeo.STNumPoints( ) ).STY
insert @geos values
(
@subGeoIdx,
@subGeo,
@startX,
@endX,
@startY,
@endY,
@subGeo.STNumPoints() ,
case when @subGeoIdx = 1 then 1 when @oldEndX = @startX and @oldEndY = @startY then 1 else 0 end
)
select
@oldEndX = @endX,
@oldEndY = @endY,
@subGeoIdx = @subGeoIdx + 1
end
if not exists ( select * from @geos where ContinueFromPrevious = 0 ) --> then all LineStrings are connected
begin
--> build a single LINESTRING( )...
select @sql = ''
declare c cursor for select SubGeo, StartX, EndX, StartY, EndY, NumPoints, ContinueFromPrevious from @geos order by Idx
open c
while ( 1 = 1 )
begin
fetch next from c into @subGeo, @startX, @endX, @startY, @endY, @numPoints, @continue
if @@fetch_status != 0 break;
select @idx = case when @started = 0 then 1 else 2 end, @started = 1 --> accrue all points, de-duplicating line ends...
while ( @idx <= @numPoints )
begin
select @point = @subGeo.STPointN( @idx )
select @sql += convert( nvarchar, @point.STX ) + N' ' + convert( nvarchar, @point.STY ) + N','
select @idx = @idx + 1
end
end
close c
deallocate c
select @sql = substring( @sql, 1, len( @sql ) -1 )
select @result = geometry::STGeomFromText(N'LINESTRING(' + @sql + N')', 0 )
end
else --> we have disjoint lines in the inputs...
begin
select @sql = N'', @started = 0
--> build a MULTILINESTRING((),()...) with line segements terminated at disjoint points..
declare c cursor for select SubGeo, StartX, EndX, StartY, EndY, NumPoints, ContinueFromPrevious from @geos order by Idx
open c
while ( 1=1 )
begin
fetch next from c into @subGeo, @startX, @endX, @startY, @endY, @numPoints, @continue
if @@fetch_status != 0 break;
if @continue = 1
begin
select @idx = case when @started = 0 then 1 else 2 end, @started = 1
while ( @idx <= @numPoints )
begin
select @point = @subGeo.STPointN( @idx )
select @sql += convert( nvarchar, @point.STX ) + N' ' + convert( nvarchar, @point.STY ) + N','
select @idx = @idx + 1
end
end
else
begin
insert @multiLines values ( @segment, substring( @sql, 1, len( @sql ) -1 ) ) --> collect the segment
select @idx = 1, @sql = N'', @segment = @segment + 1
while ( @idx <= @numPoints )
begin
select @point = @subGeo.STPointN( @idx )
select @sql += convert( nvarchar, @point.STX ) + N' ' + convert( nvarchar, @point.STY ) + N','
select @idx = @idx + 1
end
end
end
close c
deallocate c
insert @multiLines values ( @segment, substring( @sql, 1, len( @sql ) -1 ) )
select @sql = N''
select @sql += N'(' + Segment + N'),' from @multiLines order by Idx --> appends all segments
select @sql = substring( @sql, 1, len( @sql ) -1 )
select @result = geometry::STGeomFromText( 'MULTILINESTRING('+ @sql + N')', 1 )
end
...最后给出:
DECLARE @Geom TABLE
(
shape geometry,
shapeType nvarchar(50)
);
INSERT @Geom(shape,shapeType) VALUES
('LINESTRING(1 2, 3 4)', 'A'),
('LINESTRING(3 4, 9 9)', 'B'), --> disjoint from here to the next LINESTRING
('LINESTRING(9 8, 3 4)', 'C'),
('LINESTRING(3 4, 1 2)', 'D');
select
dbo.SimplifyToLine(geometry::CollectionAggregate(shape)).ToString(),
dbo.SimplifyToLine(geometry::CollectionAggregate(shape))
from
@Geom
delete @Geom
INSERT @Geom(shape,shapeType) VALUES
('LINESTRING(1 2, 3 4)', 'A'),
('LINESTRING(3 4, 9 8)', 'B'),
('LINESTRING(9 8, 3 4)', 'C'),
('LINESTRING(3 4, 1 2)', 'D');
select
dbo.SimplifyToLine(geometry::CollectionAggregate(shape)).ToString(),
dbo.SimplifyToLine(geometry::CollectionAggregate(shape))
from
@Geom
...你得到:
与 Clay 传递 GeometryCollection 的想法不同,我实现了一个强大的版本,它将采用 POINT、MULTIPOINT、LINESTRING、MULTILINESTRING 的任意组合,并删除 @Tolerance
中的任何接触端点并创建一个 POINT、LINESTRING , 多行
这是它的工作演示(注意 0 和 0.1 的公差如何影响第二和第三个输出):
DECLARE @GeometryCollection GEOMETRY = GEOMETRY::STGeomFromText('GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4), LINESTRING (3 4, 100 100), LINESTRING (9 8, 3 4), LINESTRING (3 4, 1 2), POINT(1 2), POINT(1 2), POINT(1 2))',0)
SELECT [dbo].[fnSimplifyToLine](@GeometryCollection, 0).ToString();
--Output: MULTILINESTRING ((1 2, 3 4, 100 100), (9 8, 3 4, 1 2))
SET @GeometryCollection = GEOMETRY::STGeomFromText('GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4.1), LINESTRING (3 4, 9 9, 6 1))',0)
SELECT [dbo].[fnSimplifyToLine](@GeometryCollection, 0).ToString()
--Output: MULTILINESTRING ((1 2, 3 4.1), (3 4, 9 9, 6 1))
SET @GeometryCollection = GEOMETRY::STGeomFromText('GEOMETRYCOLLECTION (LINESTRING (1 2, 3 4.1), LINESTRING (3 4, 9 9, 6 1))',0)
SELECT [dbo].[fnSimplifyToLine](@GeometryCollection, 0.1).ToString()
--Output: LINESTRING (1 2, 3 4.1, 9 9, 6 1)
SET @GeometryCollection = GEOMETRY::STGeomFromText('GEOMETRYCOLLECTION (POINT(1 2))',0)
SELECT [dbo].[fnSimplifyToLine](@GeometryCollection, 0).ToString()
--Output: POINT (1 2)
SET @GeometryCollection = GEOMETRY::STGeomFromText('GEOMETRYCOLLECTION (MULTIPOINT((1 2), (2 3)))',0)
SELECT [dbo].[fnSimplifyToLine](@GeometryCollection, 0).ToString()
--Output: (1 2, 2 3)
首先,我必须创建一个递归 CTE 函数,它采用几何并提取所有点。
CREATE FUNCTION [dbo].[fnGetPoints]
(
@Geometry GEOMETRY
)
RETURNS TABLE
AS
RETURN
(
WITH GeometryPoints(N, Point) AS (
SELECT
CAST(1 AS DECIMAL(9,2)) as N
,@Geometry.STPointN(1) as Point
UNION ALL
SELECT
CAST(N + 1.0 AS DECIMAL(9,2)) as N
,@Geometry.STPointN(N + 1) as Point
FROM GeometryPoints GP
WHERE N < @Geometry.STNumPoints()
)
SELECT *
FROM GeometryPoints
)
然后我创建了一个函数,将 fnGetPoints
交叉应用到 @GeometryCollection
中的每个几何体以获得点矩阵。使用窗口函数 (LAG) 查找端点在 @Tolerance
内的位置并删除这些点。然后我做了一个 data smear 来组合它们共享端点的几何图形。
CREATE FUNCTION [dbo].[fnSimplifyToLine] (@GeometryCollection GEOMETRY, @Tolerance DECIMAL(19,10))
RETURNS GEOMETRY
AS
BEGIN
DECLARE @PointMatrix TABLE (
PointId INT,
LinestringId INT,
GeometryIndex INT,
GeometryType varchar(100),
PointIndex INT,
Point GEOMETRY,
Duplicate BIT
);
DECLARE @Linestrings TABLE (
LinestringId INT,
PointArrayStr varchar(max)
);
WITH CollectionGeometries(N, Geom) AS (
SELECT
CAST(1 AS DECIMAL(9,2)) as N
,@GeometryCollection.STGeometryN(1) as Geom
UNION ALL
SELECT
CAST(N + 1.0 AS DECIMAL(9,2)) as N
, @GeometryCollection.STGeometryN(N + 1) as Geom
FROM CollectionGeometries CG
WHERE N < @GeometryCollection.STNumGeometries()
), PointMatrix AS (
SELECT
ROW_NUMBER() OVER(ORDER BY G.N, P.N) as PointId
,G.N as GeometryIndex
,G.Geom.STGeometryType() as GeometryType
,P.N as PointIndex
,P.Point
FROM CollectionGeometries G
CROSS APPLY dbo.fnGetPoints(Geom) P
)
INSERT INTO @PointMatrix
SELECT
PointId
,GeometryIndex as LinestringId
,GeometryIndex
,GeometryType
,PointIndex
,Point
,CASE
WHEN
GeometryIndex != LAG(GeometryIndex) OVER(ORDER BY PointId)
AND ABS(Point.STX - LAG(Point.STX) OVER(ORDER BY PointId)) <= @Tolerance
AND ABS(Point.STY - LAG(Point.STY) OVER(ORDER BY PointId)) <= @Tolerance
THEN 1
ELSE 0
END as Duplicate
FROM PointMatrix
OPTION (MAXRECURSION 10000)
-- POLYGON, MULTIPOLYGON, GEOMETRYCOLLECTION, CIRCULARSTRING, COMPOUNDCURVE, CURVEPOLYGON not supported
IF EXISTS ( SELECT * FROM @PointMatrix WHERE GeometryType NOT IN ('POINT', 'MULTIPOINT', 'LINESTRING', 'MULTILINESTRING'))
RETURN CAST('Geometries in @GeometryCollection must all be IN (''POINT'',''MULTIPOINT'', ''LINESTRING'', ''MULTILINESTRING'')' as GEOMETRY);
DECLARE @SRID INT = (SELECT DISTINCT Point.STSrid FROM @PointMatrix)
UPDATE @PointMatrix
SET LinestringId = NULL
WHERE GeometryIndex IN (
SELECT GeometryIndex FROM @PointMatrix WHERE Duplicate = 1
)
DELETE @PointMatrix
WHERE Duplicate = 1;
-- Data smear
WITH Cnt AS (
SELECT PointId, Point, LinestringId,c=COUNT(LinestringId) OVER (ORDER BY PointId)
FROM @PointMatrix
), SmearedLineStringId AS (
SELECT PointId, Point, LinestringId=MAX(LinestringId) OVER (PARTITION BY c)
FROM Cnt
)
INSERT @Linestrings
SELECT
LinestringId
,'(' +
STUFF((
SELECT ',' + CAST(Point.STX as varchar(100)) + ' ' + CAST(Point.STY as varchar(100))
FROM SmearedLineStringId t2
WHERE t1.LinestringId = t2.LinestringId
ORDER BY PointId
FOR XML PATH ('')
), 1, 1, '')
+ ')' as PointArray
FROM SmearedLineStringId t1
GROUP BY LinestringId
DECLARE @Type varchar(100) = CASE
WHEN 1 =(SELECT COUNT(*) FROM @PointMatrix) THEN
'POINT'
WHEN 1 =(SELECT COUNT(*) FROM @Linestrings) THEN
'LINESTRING'
ELSE
'MULTILINESTRING'
END
DECLARE @BeginParens char(1) = '(';
DECLARE @EndParens char(1) = ')'
IF @Type != 'MULTILINESTRING'
BEGIN
SET @BeginParens = '';
SET @EndParens = '';
END
DECLARE @Wkt varchar(max) = @Type + @BeginParens +
STUFF((
SELECT ',' + PointArrayStr
FROM @Linestrings t2
ORDER BY LinestringId
FOR XML PATH ('')
), 1, 1, '')
+ @EndParens
RETURN Geometry::STGeomFromText(@Wkt, @SRID)
END
GO