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 }