denizzzka / dpq2
1
///
2
module dpq2.conv.geometric;
3

4
import dpq2.oids: OidType;
5
import dpq2.value: ConvExceptionType, throwTypeComplaint, Value, ValueConvException, ValueFormat;
6
import std.bitmanip: bigEndianToNative, nativeToBigEndian;
7
import std.traits;
8
import std.range.primitives: ElementType;
9

10
@safe:
11

12
private template GetRvalueOfMember(T, string memberName)
13
{
14
    mixin("alias MemberType = typeof(T."~memberName~");");
15

16
    static if(is(MemberType == function))
17
        alias R = ReturnType!(MemberType);
18
    else
19
        alias R = MemberType;
20

21
    alias GetRvalueOfMember = R;
22
}
23

24
/// Checks that type have "x" and "y" members of returning type "double"
25
bool isValidPointType(T)()
26
{
27
    static if(__traits(compiles, typeof(T.x)) && __traits(compiles, typeof(T.y)))
28
    {
29 1
        return
30
            is(GetRvalueOfMember!(T, "x") == double) &&
31
            is(GetRvalueOfMember!(T, "y") == double);
32
    }
33
    else
34 1
        return false;
35
}
36

37
unittest
38
{
39
    {
40
        struct PT {double x; double y;}
41 1
        assert(isValidPointType!PT);
42
    }
43

44
    {
45
        struct InvalidPT {double x;}
46 1
        assert(!isValidPointType!InvalidPT);
47
    }
48
}
49

50
/// Checks that type have "min" and "max" members of suitable returning type of point
51
bool isValidBoxType(T)()
52
{
53
    static if(__traits(compiles, typeof(T.min)) && __traits(compiles, typeof(T.max)))
54
    {
55
        return
56
            isValidPointType!(GetRvalueOfMember!(T, "min")) &&
57
            isValidPointType!(GetRvalueOfMember!(T, "max"));
58
    }
59
    else
60
        return false;
61
}
62

63
///
64
bool isValidLineSegmentType(T)()
65
{
66
    static if(__traits(compiles, typeof(T.start)) && __traits(compiles, typeof(T.end)))
67
    {
68
        return
69
            isValidPointType!(GetRvalueOfMember!(T, "start")) &&
70
            isValidPointType!(GetRvalueOfMember!(T, "end"));
71
    }
72
    else
73
        return false;
74
}
75

76
///
77
bool isValidPolygon(T)()
78
{
79 1
    return isArray!T && isValidPointType!(ElementType!T);
80
}
81

82
unittest
83
{
84
    struct PT {double x; double y;}
85 1
    assert(isValidPolygon!(PT[]));
86 1
    assert(!isValidPolygon!(PT));
87
}
88

89
private auto serializePoint(Vec2Ddouble, T)(Vec2Ddouble point, T target)
90
if(isValidPointType!Vec2Ddouble)
91
{
92
    import std.algorithm : copy;
93

94 1
    auto rem = point.x.nativeToBigEndian[0 .. $].copy(target);
95 1
    rem = point.y.nativeToBigEndian[0 .. $].copy(rem);
96

97 1
    return rem;
98
}
99

100
Value toValue(Vec2Ddouble)(Vec2Ddouble pt)
101
if(isValidPointType!Vec2Ddouble)
102
{
103 1
    ubyte[] data = new ubyte[16];
104 1
    pt.serializePoint(data);
105

106 1
    return createValue(data, OidType.Point);
107
}
108

109
private auto serializeBox(Box, T)(Box box, T target)
110
{
111 1
    auto rem = box.max.serializePoint(target);
112 1
    rem = box.min.serializePoint(rem);
113

114 1
    return rem;
115
}
116

117
Value toValue(Box)(Box box)
118
if(isValidBoxType!Box)
119
{
120 1
    ubyte[] data = new ubyte[32];
121 1
    box.serializeBox(data);
122

123 1
    return createValue(data, OidType.Box);
124
}
125

126
/// Infinite line - {A,B,C} (Ax + By + C = 0)
127
struct Line
128
{
129
    double a; ///
130
    double b; ///
131
    double c; ///
132
}
133

134
///
135
struct Path(Point)
136
if(isValidPointType!Point)
137
{
138
    bool isClosed; ///
139
    Point[] points; ///
140
}
141

142
///
143
struct Circle(Point)
144
if(isValidPointType!Point)
145
{
146
    Point center; ///
147
    double radius; ///
148
}
149

150
Value toValue(T)(T line)
151
if(is(T == Line))
152
{
153
    import std.algorithm : copy;
154

155 1
    ubyte[] data = new ubyte[24];
156

157 1
    auto rem = line.a.nativeToBigEndian[0 .. $].copy(data);
158 1
    rem = line.b.nativeToBigEndian[0 .. $].copy(rem);
159 1
    rem = line.c.nativeToBigEndian[0 .. $].copy(rem);
160

161 1
    return createValue(data, OidType.Line);
162
}
163

164
Value toValue(LineSegment)(LineSegment lseg)
165
if(isValidLineSegmentType!LineSegment)
166
{
167 1
    ubyte[] data = new ubyte[32];
168

169 1
    auto rem = lseg.start.serializePoint(data);
170 1
    rem = lseg.end.serializePoint(rem);
171

172 1
    return createValue(data, OidType.LineSegment);
173
}
174

175
Value toValue(T)(T path)
176
if(isInstanceOf!(Path, T))
177
{
178
    import std.algorithm : copy;
179

180 1
    if(path.points.length < 1)
181 0
        throw new ValueConvException(ConvExceptionType.SIZE_MISMATCH,
182
            "At least one point is needed for Path", __FILE__, __LINE__);
183

184 1
    ubyte[] data = new ubyte[path.points.length * 16 + 5];
185

186 1
    ubyte isClosed = path.isClosed ? 1 : 0;
187 1
    auto rem = [isClosed].copy(data);
188 1
    rem = (cast(int) path.points.length).nativeToBigEndian[0 .. $].copy(rem);
189

190 1
    foreach (ref p; path.points)
191
    {
192 1
        rem = p.serializePoint(rem);
193
    }
194

195 1
    return createValue(data, OidType.Path);
196
}
197

198
Value toValue(Polygon)(Polygon poly)
199
if(isValidPolygon!Polygon)
200
{
201
    import std.algorithm : copy;
202

203 1
    if(poly.length < 1)
204 0
        throw new ValueConvException(ConvExceptionType.SIZE_MISMATCH,
205
            "At least one point is needed for Polygon", __FILE__, __LINE__);
206

207 1
    ubyte[] data = new ubyte[poly.length * 16 + 4];
208 1
    auto rem = (cast(int)poly.length).nativeToBigEndian[0 .. $].copy(data);
209

210 1
    foreach (ref p; poly)
211 1
        rem = p.serializePoint(rem);
212

213 1
    return createValue(data, OidType.Polygon);
214
}
215

216
Value toValue(T)(T c)
217
if(isInstanceOf!(Circle, T))
218
{
219
    import std.algorithm : copy;
220

221 1
    ubyte[] data = new ubyte[24];
222 1
    auto rem = c.center.serializePoint(data);
223 1
    c.radius.nativeToBigEndian[0 .. $].copy(rem);
224

225 1
    return createValue(data, OidType.Circle);
226
}
227

228
/// Caller must ensure that reference to the data will not be passed to elsewhere
229
private Value createValue(const ubyte[] data, OidType oid) pure @trusted
230
{
231 1
    return Value(cast(immutable) data, oid);
232
}
233

234
private alias AE = ValueConvException;
235
private alias ET = ConvExceptionType;
236

237
/// Convert to Point
238
Vec2Ddouble binaryValueAs(Vec2Ddouble)(in Value v)
239
if(isValidPointType!Vec2Ddouble)
240
{
241 1
    if(!(v.oidType == OidType.Point))
242 1
        throwTypeComplaint(v.oidType, "Point", __FILE__, __LINE__);
243

244 1
    auto data = v.data;
245

246 1
    if(!(data.length == 16))
247 1
        throw new AE(ET.SIZE_MISMATCH,
248
            "Value length isn't equal to Postgres Point size", __FILE__, __LINE__);
249

250 1
    return pointFromBytes!Vec2Ddouble(data[0..16]);
251
}
252

253
private Vec2Ddouble pointFromBytes(Vec2Ddouble)(in ubyte[16] data) pure
254
if(isValidPointType!Vec2Ddouble)
255
{
256 1
    return Vec2Ddouble(data[0..8].bigEndianToNative!double, data[8..16].bigEndianToNative!double);
257
}
258

259
T binaryValueAs(T)(in Value v)
260
if (is(T == Line))
261
{
262 1
    if(!(v.oidType == OidType.Line))
263 1
        throwTypeComplaint(v.oidType, "Line", __FILE__, __LINE__);
264

265 1
    if(!(v.data.length == 24))
266 1
        throw new AE(ET.SIZE_MISMATCH,
267
            "Value length isn't equal to Postgres Line size", __FILE__, __LINE__);
268

269 1
    return Line((v.data[0..8].bigEndianToNative!double), v.data[8..16].bigEndianToNative!double, v.data[16..24].bigEndianToNative!double);
270
}
271

272
LineSegment binaryValueAs(LineSegment)(in Value v)
273
if(isValidLineSegmentType!LineSegment)
274
{
275 1
    if(!(v.oidType == OidType.LineSegment))
276 1
        throwTypeComplaint(v.oidType, "LineSegment", __FILE__, __LINE__);
277

278 1
    if(!(v.data.length == 32))
279 1
        throw new AE(ET.SIZE_MISMATCH,
280
            "Value length isn't equal to Postgres LineSegment size", __FILE__, __LINE__);
281

282
    alias Point = ReturnType!(LineSegment.start);
283

284 1
    auto start = v.data[0..16].pointFromBytes!Point;
285 1
    auto end = v.data[16..32].pointFromBytes!Point;
286

287 1
    return LineSegment(start, end);
288
}
289

290
Box binaryValueAs(Box)(in Value v)
291
if(isValidBoxType!Box)
292
{
293 1
    if(!(v.oidType == OidType.Box))
294 1
        throwTypeComplaint(v.oidType, "Box", __FILE__, __LINE__);
295

296 1
    if(!(v.data.length == 32))
297 1
        throw new AE(ET.SIZE_MISMATCH,
298
            "Value length isn't equal to Postgres Box size", __FILE__, __LINE__);
299

300
    alias Point = typeof(Box.min);
301

302 1
    Box res;
303 1
    res.max = v.data[0..16].pointFromBytes!Point;
304 1
    res.min = v.data[16..32].pointFromBytes!Point;
305

306 1
    return res;
307
}
308

309
T binaryValueAs(T)(in Value v)
310
if(isInstanceOf!(Path, T))
311
{
312
    import std.array : uninitializedArray;
313

314 1
    if(!(v.oidType == OidType.Path))
315 1
        throwTypeComplaint(v.oidType, "Path", __FILE__, __LINE__);
316

317 1
    if(!((v.data.length - 5) % 16 == 0))
318 1
        throw new AE(ET.SIZE_MISMATCH,
319
            "Value length isn't equal to Postgres Path size", __FILE__, __LINE__);
320

321 1
    T res;
322 1
    res.isClosed = v.data[0..1].bigEndianToNative!byte == 1;
323 1
    int len = v.data[1..5].bigEndianToNative!int;
324

325 1
    if (len != (v.data.length - 5)/16)
326 1
        throw new AE(ET.SIZE_MISMATCH, "Path points number mismatch", __FILE__, __LINE__);
327

328
    alias Point = typeof(T.points[0]);
329

330 1
    res.points = uninitializedArray!(Point[])(len);
331 1
    for (int i=0; i<len; i++)
332
    {
333 1
        const ubyte[] b = v.data[ i*16+5 .. i*16+5+16 ];
334 1
        res.points[i] = b[0..16].pointFromBytes!Point;
335
    }
336

337 1
    return res;
338
}
339

340
Polygon binaryValueAs(Polygon)(in Value v)
341
if(isValidPolygon!Polygon)
342
{
343
    import std.array : uninitializedArray;
344

345 1
    if(!(v.oidType == OidType.Polygon))
346 1
        throwTypeComplaint(v.oidType, "Polygon", __FILE__, __LINE__);
347

348 1
    if(!((v.data.length - 4) % 16 == 0))
349 1
        throw new AE(ET.SIZE_MISMATCH,
350
            "Value length isn't equal to Postgres Polygon size", __FILE__, __LINE__);
351

352 1
    Polygon res;
353 1
    int len = v.data[0..4].bigEndianToNative!int;
354

355 1
    if (len != (v.data.length - 4)/16)
356 1
        throw new AE(ET.SIZE_MISMATCH, "Path points number mismatch", __FILE__, __LINE__);
357

358
    alias Point = ElementType!Polygon;
359

360 1
    res = uninitializedArray!(Point[])(len);
361 1
    for (int i=0; i<len; i++)
362
    {
363 1
        const ubyte[] b = v.data[(i*16+4)..(i*16+16+4)];
364 1
        res[i] = b[0..16].pointFromBytes!Point;
365
    }
366

367 1
    return res;
368
}
369

370
T binaryValueAs(T)(in Value v)
371
if(isInstanceOf!(Circle, T))
372
{
373 1
    if(!(v.oidType == OidType.Circle))
374 1
        throwTypeComplaint(v.oidType, "Circle", __FILE__, __LINE__);
375

376 1
    if(!(v.data.length == 24))
377 1
        throw new AE(ET.SIZE_MISMATCH,
378
            "Value length isn't equal to Postgres Circle size", __FILE__, __LINE__);
379

380
    alias Point = typeof(T.center);
381

382 1
    return T(
383
        v.data[0..16].pointFromBytes!Point,
384
        v.data[16..24].bigEndianToNative!double
385
    );
386
}
387

388
version (integration_tests)
389
package mixin template GeometricInstancesForIntegrationTest()
390
{
391
    @safe:
392

393
    import gfm.math;
394
    import dpq2.conv.geometric: Circle, Path;
395

396
    alias Point = vec2d;
397
    alias Box = box2d;
398
    static struct LineSegment
399
    {
400
        seg2d seg;
401
        alias seg this;
402

403 1
        ref Point start(){ return a; }
404 1
        ref Point end(){ return b; }
405

406 1
        this(Point a, Point b)
407
        {
408 1
            seg.a = a;
409 1
            seg.b = b;
410
        }
411
    }
412
    alias TestPath = Path!Point;
413
    alias Polygon = Point[];
414
    alias TestCircle = Circle!Point;
415
}
416

417
version (integration_tests)
418
unittest
419
{
420
    mixin GeometricInstancesForIntegrationTest;
421

422
    // binary write/read
423
    {
424 1
        auto pt = Point(1,2);
425 1
        assert(pt.toValue.binaryValueAs!Point == pt);
426

427 1
        auto ln = Line(1,2,3);
428 1
        assert(ln.toValue.binaryValueAs!Line == ln);
429

430 1
        auto lseg = LineSegment(Point(1,2),Point(3,4));
431 1
        assert(lseg.toValue.binaryValueAs!LineSegment == lseg);
432

433 1
        auto b = Box(Point(2,2), Point(1,1));
434 1
        assert(b.toValue.binaryValueAs!Box == b);
435

436 1
        auto p = TestPath(false, [Point(1,1), Point(2,2)]);
437 1
        assert(p.toValue.binaryValueAs!TestPath == p);
438

439 1
        p = TestPath(true, [Point(1,1), Point(2,2)]);
440 1
        assert(p.toValue.binaryValueAs!TestPath == p);
441

442 1
        Polygon poly = [Point(1,1), Point(2,2), Point(3,3)];
443 1
        assert(poly.toValue.binaryValueAs!Polygon == poly);
444

445 1
        auto c = TestCircle(Point(1,2), 3);
446 1
        assert(c.toValue.binaryValueAs!TestCircle == c);
447
    }
448

449
    // Invalid OID tests
450
    {
451
        import std.exception : assertThrown;
452

453 1
        auto v = Point(1,1).toValue;
454 1
        v.oidType = OidType.Text;
455 1
        assertThrown!ValueConvException(v.binaryValueAs!Point);
456

457 1
        v = Line(1,2,3).toValue;
458 1
        v.oidType = OidType.Text;
459 1
        assertThrown!ValueConvException(v.binaryValueAs!Line);
460

461 1
        v = LineSegment(Point(1,1), Point(2,2)).toValue;
462 1
        v.oidType = OidType.Text;
463 1
        assertThrown!ValueConvException(v.binaryValueAs!LineSegment);
464

465 1
        v = Box(Point(1,1), Point(2,2)).toValue;
466 1
        v.oidType = OidType.Text;
467 1
        assertThrown!ValueConvException(v.binaryValueAs!Box);
468

469 1
        v = TestPath(true, [Point(1,1), Point(2,2)]).toValue;
470 1
        v.oidType = OidType.Text;
471 1
        assertThrown!ValueConvException(v.binaryValueAs!TestPath);
472

473 1
        v = [Point(1,1), Point(2,2)].toValue;
474 1
        v.oidType = OidType.Text;
475 1
        assertThrown!ValueConvException(v.binaryValueAs!Polygon);
476

477 1
        v = TestCircle(Point(1,1), 3).toValue;
478 1
        v.oidType = OidType.Text;
479 1
        assertThrown!ValueConvException(v.binaryValueAs!TestCircle);
480
    }
481

482
    // Invalid data size
483
    {
484
        import std.exception : assertThrown;
485

486 1
        auto v = Point(1,1).toValue;
487 1
        v._data = new ubyte[1];
488 1
        assertThrown!ValueConvException(v.binaryValueAs!Point);
489

490 1
        v = Line(1,2,3).toValue;
491 1
        v._data.length = 1;
492 1
        assertThrown!ValueConvException(v.binaryValueAs!Line);
493

494 1
        v = LineSegment(Point(1,1), Point(2,2)).toValue;
495 1
        v._data.length = 1;
496 1
        assertThrown!ValueConvException(v.binaryValueAs!LineSegment);
497

498 1
        v = Box(Point(1,1), Point(2,2)).toValue;
499 1
        v._data.length = 1;
500 1
        assertThrown!ValueConvException(v.binaryValueAs!Box);
501

502 1
        v = TestPath(true, [Point(1,1), Point(2,2)]).toValue;
503 1
        v._data.length -= 16;
504 1
        assertThrown!ValueConvException(v.binaryValueAs!TestPath);
505 1
        v._data.length = 1;
506 1
        assertThrown!ValueConvException(v.binaryValueAs!TestPath);
507

508 1
        v = [Point(1,1), Point(2,2)].toValue;
509 1
        v._data.length -= 16;
510 1
        assertThrown!ValueConvException(v.binaryValueAs!Polygon);
511 1
        v._data.length = 1;
512 1
        assertThrown!ValueConvException(v.binaryValueAs!Polygon);
513

514 1
        v = TestCircle(Point(1,1), 3).toValue;
515 1
        v._data.length = 1;
516 1
        assertThrown!ValueConvException(v.binaryValueAs!TestCircle);
517
    }
518
}

Read our documentation on viewing source code .

Loading