I want to convert a Well known binary string WKB into a geometrical object in Delphi. I read the content from an MSSQL DB with this command
geomString := FieldbyName('Geom').AsString;
As a next step I must convert this information into a Polygon. All my data are just simple polygons. I have written this small unit, I did not find any starting point somewhere else.
interface
uses
System.SysUtils, System.Classes, System.Math, System.Types;
type
WKBByteOrder = (wkbXDR, // Big Endian
wkbNDR // Little Endian
);
/// <summary>
/// Well-known text (WKT) is a text markup language for representing vector
/// geometry objects. A binary equivalent, known as well-known binary (WKB)
/// </summary>
/// <remarks>
/// <para>
/// see also:
/// </para>
/// <para>
/// https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry#Well-known_binary
/// https://libgeos.org/specifications/wkb/
/// </para>
/// </remarks>
WKBGeometryType = (wkbPoint = 1, wkbLineString = 2, wkbPolygon = 3,
wkbTriangle = 17, wkbMultiPoint = 4, wkbMultiLineString = 5,
wkbMultiPolygon = 6, wkbGeometryCollection = 7, wkbPolyhedralSurface = 15,
wkbTIN = 16, wkbPointZ = 1001, wkbLineStringZ = 1002, wkbPolygonZ = 1003,
wkbTriangleZ = 1017, wkbMultiPointZ = 1004, wkbMultiLineStringZ = 1005,
wkbMultiPolygonZ = 1006, wkbGeometryCollectionZ = 1007,
wkbPolyhedralSurfaceZ = 1015, wkbTINZ = 1016, wkbPointM = 2001,
wkbLineStringM = 2002, wkbPolygonM = 2003, wkbTriangleM = 2017,
wkbMultiPointM = 2004, wkbMultiLineStringM = 2005, wkbMultiPolygonM = 2006,
wkbGeometryCollectionM = 2007, wkbPolyhedralSurfaceM = 2015, wkbTINM = 2016,
wkbPointZM = 3001, wkbLineStringZM = 3002, wkbPolygonZM = 3003,
wkbTriangleZM = 3017, wkbMultiPointZM = 3004, wkbMultiLineStringZM = 3005,
wkbMultiPolygonZM = 3006, wkbGeometryCollectionZM = 3007,
wkbPolyhedralSurfaceZM = 3015, wkbTinZM = 3016);
type
TWKBReadError = class(Exception);
type
TPolygon = TArray<TPointF>;
function WellknownBinary2Geom(const s: string): TPolygon;
implementation
procedure ReverseBytes(var Bytes: TBytes);
var
i: Integer;
Temp: Byte;
begin
for i := 0 to (Length(Bytes) div 2) - 1 do
begin
Temp := Bytes[i];
Bytes[i] := Bytes[Length(Bytes) - 1 - i];
Bytes[Length(Bytes) - 1 - i] := Temp;
end;
end;
function ReadDouble(var Buffer: TBytes; var Offset: Integer;
IsBigEndian: Boolean): Double;
var
Bytes: TBytes;
begin
if Offset + SizeOf(Double) > Length(Buffer) then
raise TWKBReadError.Create('Buffer overflow while reading double');
SetLength(Bytes, SizeOf(Double));
Move(Buffer[Offset], Bytes[0], SizeOf(Double));
Inc(Offset, SizeOf(Double));
if IsBigEndian then
ReverseBytes(Bytes);
Move(Bytes[0], Result, SizeOf(Double));
end;
function ReadUInt32(var Buffer: TBytes; var Offset: Integer;
IsBigEndian: Boolean): UInt32;
var
Bytes: TBytes;
begin
if Offset + SizeOf(UInt32) > Length(Buffer) then
raise TWKBReadError.Create('Buffer overflow while reading UInt32');
SetLength(Bytes, SizeOf(UInt32));
Move(Buffer[Offset], Bytes[0], SizeOf(UInt32));
Inc(Offset, SizeOf(UInt32));
if IsBigEndian then
ReverseBytes(Bytes);
Move(Bytes[0], Result, SizeOf(UInt32));
end;
function WellknownBinary2Geom(const s: string): TPolygon;
var
Buffer: TBytes;
Offset: Integer;
ByteOrder: Byte;
WKBType: UInt32;
NumRings: UInt32;
NumPoints: UInt32;
Points: TPolygon;
IsBigEndian: Boolean;
begin
// Convert input string to byte array
Buffer := TEncoding.UTF8.GetBytes(s);
Offset := 0;
// Read byte order
ByteOrder := Buffer[Offset];
Inc(Offset);
IsBigEndian := ByteOrder = 0;
// Read WKB type
WKBType := ReadUInt32(Buffer, Offset, IsBigEndian);
if WKBType <> Ord(wkbPolygon) then
raise TWKBReadError.Create('Unsupported WKB type');
// Read number of rings
NumRings := ReadUInt32(Buffer, Offset, IsBigEndian);
if NumRings <> 1 then
raise TWKBReadError.Create('Unsupported number of rings');
// Read number of points in the exterior ring
NumPoints := ReadUInt32(Buffer, Offset, IsBigEndian);
SetLength(Points, NumPoints);
// Read points
for var i := 0 to NumPoints - 1 do
begin
Points[i].X := ReadDouble(Buffer, Offset, IsBigEndian);
Points[i].Y := ReadDouble(Buffer, Offset, IsBigEndian);
end;
Result := Points;
end;
end.
Debugging my data I already fail getting the expected WKBGeometryType and pretty useless values for NumRings, NumPoints which makes it pretty hard to continue.
The data on the SQL server are correct, I can verify with this function in Delphi :
function SpatialIntegrityAsText(FServername, FDatabasename, FTableName,
FFieldName: string; ID: Integer): String;
var
FQuery: TQuery;
FConnection: TTConnection;
SQLSTR: string;
begin
FConnection := TConnection.create(nil);
FQuery := TQuery.create(nil);
try
FConnection.LoginPrompt := False;
FQuery.Connection := FConnection;
DBConnect(FServername, FDatabasename, FConnection);
SQLSTR := 'SELECT ' + FFieldName + '.STAsText() FROM ' + FTableName +
' where REcordIndex=' + IntToStr(ID);
// SQLSTR := 'SELECT ' + FFieldName + '.ToString() FROM ' + FTableName +
// ' where REcordIndex=' + IntToStr(ID);
FQuery.SQL.add(SQLSTR);
FQuery.Open;
Result := FQuery.Fields[0].asString + '<END>';
finally
FQuery.Free;
FConnection.Free;
end;
end;