1 // Written in D programming language
2 /**
3 *   PostgreSQL geometric types binary format.
4 *
5 *   Copyright: © 2014 DSoftOut
6 *   License: Subject to the terms of the MIT license, as written in the included LICENSE file.
7 *   Authors: NCrashed <ncrashed@gmail.com>
8 */
9 module pgator.db.pq.types.geometric;
10 
11 import pgator.db.pq.types.oids;
12 import std.array;
13 import std.bitmanip;
14 import std.conv;
15 import std.math;
16 
17 struct Point
18 {
19     double x, y;
20     
21     string toString() const
22     {
23         return text("(",x,",",y,")");
24     }
25     
26     bool opEquals(const Point b) const
27     {
28         return x.approxEqual(b.x) && y.approxEqual(b.y); 
29     }
30 }
31 
32 struct LineSegment
33 {
34     double x1, y1, x2, y2;
35     
36     string toString() const
37     {
38         return text("((",x1,",",y1,"),","(",x2,",",y2,"))");
39     }
40     
41     bool opEquals(const LineSegment b) const
42     {
43         return x1.approxEqual(b.x1) && y1.approxEqual(b.y1) &&
44                x2.approxEqual(b.x2) && y2.approxEqual(b.y2); 
45     }
46 }
47 
48 struct Path
49 {
50     bool closed;
51     Point[] points;
52     
53     string toString() const
54     {
55         auto builder = appender!string;
56         
57         builder.put(closed ? '(' : '[');
58         foreach(i,p; points)
59         {
60             builder.put(p.to!string);
61             if(i != points.length -1) 
62                 builder.put(',');
63         }
64         builder.put(closed ? ')' : ']');
65         return builder.data;
66     }
67 }
68 
69 struct Box
70 {
71     double highx, highy, lowx, lowy;
72     
73     this(double ax, double ay, double bx, double by)
74     {
75         if(ax > bx)
76         {
77             highx = ax;
78             lowx  = bx;
79         }
80         else
81         {
82             lowx  = ax;
83             highx = bx;
84         }
85          
86         if(ay > by)
87         {
88             highy = ay;
89             lowy  = by;
90         } else
91         {
92             lowy  = ay;
93             highy = by;
94         }
95     }
96     
97     string toString() const
98     {
99         return text("((",highx,",",highy,"),","(",lowx,",",lowy,"))");
100     }
101     
102     bool opEquals(const Box b) const
103     {
104         return highx.approxEqual(b.highx) && highy.approxEqual(b.highy) &&
105                lowx.approxEqual(b.lowx) && lowy.approxEqual(b.lowy); 
106     }
107 }
108 
109 struct Polygon
110 {
111     Point[] points;
112     
113     string toString() const
114     {
115         auto builder = appender!string;
116         
117         builder.put('(');
118         foreach(i,p; points)
119         {
120             builder.put(p.to!string);
121             if(i != points.length -1) 
122                 builder.put(',');
123         }
124         builder.put(')');
125         return builder.data;
126     }
127 }
128 
129 struct Circle
130 {
131     Point center;
132     double radius;
133     
134     string toString() const
135     {
136         auto builder = appender!string;
137         
138         builder.put('<');
139         builder.put(center.to!string);
140         builder.put(',');
141         builder.put(radius.to!string);
142         builder.put('>');
143         return builder.data;
144     }
145     
146     bool opEquals(const Circle b) const
147     {
148         return center == b.center && radius.approxEqual(b.radius); 
149     }   
150 }
151 
152 Point convert(PQType type)(ubyte[] val)
153     if(type == PQType.Point)
154 {
155     assert(val.length == 16);
156     return Point(val.read!double, val.read!double);
157 }
158 
159 LineSegment convert(PQType type)(ubyte[] val)
160     if(type == PQType.LineSegment)
161 {
162     assert(val.length == double.sizeof*4);
163     double x1 = val.read!double;
164     double y1 = val.read!double;
165     double x2 = val.read!double;
166     double y2 = val.read!double;
167     return LineSegment(x1, y1, x2, y2);
168 }
169 
170 Path convert(PQType type)(ubyte[] val)
171     if(type == PQType.Path)
172 {
173     Path path;
174     path.closed = val.read!bool;
175     size_t l = cast(size_t)val.read!uint;
176     path.points = new Point[l];
177     
178     assert(val.length == 2*double.sizeof*l);
179     foreach(ref p; path.points)
180     {
181         p = Point(val.read!double, val.read!double);
182     }
183     return path;
184 }
185 
186 Box convert(PQType type)(ubyte[] val)
187     if(type == PQType.Box)
188 {
189     assert(val.length == 4*double.sizeof);
190     double highx = val.read!double;
191     double highy = val.read!double;
192     double lowx = val.read!double;
193     double lowy = val.read!double;
194     return Box(highx, highy, lowx, lowy);
195 }
196 
197 Polygon convert(PQType type)(ubyte[] val)
198     if(type == PQType.Polygon)
199 {
200     Polygon poly;
201     size_t l = val.read!uint;
202     poly.points = new Point[l];
203     
204     assert(val.length == 2*double.sizeof*l);
205     foreach(ref p; poly.points)
206     {
207         p = Point(val.read!double, val.read!double);
208     }
209     return poly;
210 }
211 
212 Circle convert(PQType type)(ubyte[] val)
213     if(type == PQType.Circle)
214 {
215     assert(val.length == 3*double.sizeof);
216     double centerx = val.read!double;
217     double centery = val.read!double;
218     double radius = val.read!double;
219 
220     return Circle(Point(centerx, centery), radius);
221 }
222 
223 version(IntegrationTest2)
224 {
225     import pgator.db.pq.types.test;
226     import pgator.db.pool;
227     import std.random;
228     import std.algorithm;
229     import dlogg.log;
230     
231     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
232         if(type == PQType.Point)
233     {
234         logger.logInfo("Testing Point...");
235           
236         foreach(i; 0..100)
237         {
238             auto test = Point(uniform(-100.0, 100.0), uniform(-100.0, 100.0));
239             testValue!(Point, (v) => "'"~v.to!string~"'")(logger, pool, test, "point");
240         }
241     }
242     
243     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
244         if(type == PQType.LineSegment)
245     {
246         logger.logInfo("Testing LineSegment...");
247           
248         foreach(i; 0..100)
249         {
250             auto test = LineSegment(uniform(-100.0, 100.0), uniform(-100.0, 100.0),
251                                     uniform(-100.0, 100.0), uniform(-100.0, 100.0));
252             testValue!(LineSegment, (v) => "'"~v.to!string~"'")(logger, pool, test, "lseg");
253         }
254     }
255     
256     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
257         if(type == PQType.Path)
258     {
259         logger.logInfo("Testing Path...");
260           
261         Path getRandPath()
262         {
263             Path path;
264             path.closed = uniform!"[]"(0,1) != 0;
265             
266             auto builder = appender!(Point[]);
267             foreach(i; 0..uniform(1,15))
268             {
269                 builder.put(Point(uniform(-100.0, 100.0), uniform(-100.0, 100.0)));
270             }
271             path.points = builder.data;
272             
273             return path;
274         }  
275         foreach(i; 0..100)
276         {
277             testValue!(Path, (v) => "'"~v.to!string~"'")(logger, pool, getRandPath, "path");
278         }
279     }
280     
281     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
282         if(type == PQType.Box)
283     {
284         logger.logInfo("Testing Box...");
285           
286         foreach(i; 0..100)
287         {
288             auto test = Box(uniform(-100.0, 100.0), uniform(-100.0, 100.0),
289                             uniform(-100.0, 100.0), uniform(-100.0, 100.0));
290             testValue!(Box, (v) => "'"~v.to!string~"'")(logger, pool, test, "box");
291         }
292     }
293     
294     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
295         if(type == PQType.Polygon)
296     {
297         logger.logInfo("Testing Polygon...");
298           
299         Polygon getRandPoly()
300         {
301             Polygon poly;
302             
303             auto builder = appender!(Point[]);
304             foreach(i; 0..uniform(1,15))
305             {
306                 builder.put(Point(uniform(-100.0, 100.0), uniform(-100.0, 100.0)));
307             }
308             poly.points = builder.data;
309             
310             return poly;
311         }  
312         foreach(i; 0..100)
313         {
314             testValue!(Polygon, (v) => "'"~v.to!string~"'")(logger, pool, getRandPoly, "polygon");
315         }
316     }
317     
318     void test(PQType type)(shared ILogger logger, shared IConnectionPool pool)
319         if(type == PQType.Circle)
320     {
321         logger.logInfo("Testing Circle...");
322           
323         foreach(i; 0..100)
324         {
325             auto test = Circle(Point(uniform(-100.0, 100.0), uniform(-100.0, 100.0)),
326                             uniform(0, 100.0));
327             testValue!(Circle, (v) => "'"~v.to!string~"'")(logger, pool, test, "circle");
328         }
329     }
330 }