1<?php
2/*
3 * (c) Patrick Hayes
4 *
5 * This code is open-source and licenced under the Modified BSD License.
6 * For the full copyright and license information, please view the LICENSE
7 * file that was distributed with this source code.
8 */
9
10/**
11 * PHP Geometry/WKB encoder/decoder
12 *
13 */
14class WKB extends GeoAdapter
15{
16
17  private $dimension = 2;
18  private $z = FALSE;
19  private $m = FALSE;
20
21  /**
22   * Read WKB into geometry objects
23   *
24   * @param string $wkb
25   *   Well-known-binary string
26   * @param bool $is_hex_string
27   *   If this is a hexedecimal string that is in need of packing
28   *
29   * @return Geometry
30   */
31  public function read($wkb, $is_hex_string = FALSE) {
32    if ($is_hex_string) {
33      $wkb = pack('H*',$wkb);
34    }
35
36    if (empty($wkb)) {
37      throw new Exception('Cannot read empty WKB geometry. Found ' . gettype($wkb));
38    }
39
40    $mem = fopen('php://memory', 'r+');
41    fwrite($mem, $wkb);
42    fseek($mem, 0);
43
44    $geometry = $this->getGeometry($mem);
45    fclose($mem);
46    return $geometry;
47  }
48
49  function getGeometry(&$mem) {
50    $base_info = unpack("corder/ctype/cz/cm/cs", fread($mem, 5));
51    if ($base_info['order'] !== 1) {
52      throw new Exception('Only NDR (little endian) SKB format is supported at the moment');
53    }
54
55    if ($base_info['z']) {
56      $this->dimension++;
57      $this->z = TRUE;
58    }
59    if ($base_info['m']) {
60      $this->dimension++;
61      $this->m = TRUE;
62    }
63
64    // If there is SRID information, ignore it - use EWKB Adapter to get SRID support
65    if ($base_info['s']) {
66      fread($mem, 4);
67    }
68
69    switch ($base_info['type']) {
70      case 1:
71        return $this->getPoint($mem);
72      case 2:
73        return $this->getLinstring($mem);
74      case 3:
75        return $this->getPolygon($mem);
76      case 4:
77        return $this->getMulti($mem,'point');
78      case 5:
79        return $this->getMulti($mem,'line');
80      case 6:
81        return $this->getMulti($mem,'polygon');
82      case 7:
83        return $this->getMulti($mem,'geometry');
84    }
85  }
86
87  function getPoint(&$mem) {
88    $point_coords = unpack("d*", fread($mem,$this->dimension*8));
89    if (!empty($point_coords)) {
90      return new Point($point_coords[1],$point_coords[2]);
91    }
92    else {
93      return new Point(); // EMPTY point
94    }
95  }
96
97  function getLinstring(&$mem) {
98    // Get the number of points expected in this string out of the first 4 bytes
99    $line_length = unpack('L',fread($mem,4));
100
101    // Return an empty linestring if there is no line-length
102    if (!$line_length[1]) return new LineString();
103
104    // Read the nubmer of points x2 (each point is two coords) into decimal-floats
105    $line_coords = unpack('d*', fread($mem,$line_length[1]*$this->dimension*8));
106
107    // We have our coords, build up the linestring
108    $components = array();
109    $i = 1;
110    $num_coords = count($line_coords);
111    while ($i <= $num_coords) {
112      $components[] = new Point($line_coords[$i],$line_coords[$i+1]);
113      $i += 2;
114    }
115    return new LineString($components);
116  }
117
118  function getPolygon(&$mem) {
119    // Get the number of linestring expected in this poly out of the first 4 bytes
120    $poly_length = unpack('L',fread($mem,4));
121
122    $components = array();
123    $i = 1;
124    while ($i <= $poly_length[1]) {
125      $components[] = $this->getLinstring($mem);
126      $i++;
127    }
128    return new Polygon($components);
129  }
130
131  function getMulti(&$mem, $type) {
132    // Get the number of items expected in this multi out of the first 4 bytes
133    $multi_length = unpack('L',fread($mem,4));
134
135    $components = array();
136    $i = 1;
137    while ($i <= $multi_length[1]) {
138      $components[] = $this->getGeometry($mem);
139      $i++;
140    }
141    switch ($type) {
142      case 'point':
143        return new MultiPoint($components);
144      case 'line':
145        return new MultiLineString($components);
146      case 'polygon':
147        return new MultiPolygon($components);
148      case 'geometry':
149        return new GeometryCollection($components);
150    }
151  }
152
153  /**
154   * Serialize geometries into WKB string.
155   *
156   * @param Geometry $geometry
157   *
158   * @return string The WKB string representation of the input geometries
159   */
160  public function write(Geometry $geometry, $write_as_hex = FALSE) {
161    // We always write into NDR (little endian)
162    $wkb = pack('c',1);
163
164    switch ($geometry->getGeomType()) {
165      case 'Point';
166        $wkb .= pack('L',1);
167        $wkb .= $this->writePoint($geometry);
168        break;
169      case 'LineString';
170        $wkb .= pack('L',2);
171        $wkb .= $this->writeLineString($geometry);
172        break;
173      case 'Polygon';
174        $wkb .= pack('L',3);
175        $wkb .= $this->writePolygon($geometry);
176        break;
177      case 'MultiPoint';
178        $wkb .= pack('L',4);
179        $wkb .= $this->writeMulti($geometry);
180        break;
181      case 'MultiLineString';
182        $wkb .= pack('L',5);
183        $wkb .= $this->writeMulti($geometry);
184        break;
185      case 'MultiPolygon';
186        $wkb .= pack('L',6);
187        $wkb .= $this->writeMulti($geometry);
188        break;
189      case 'GeometryCollection';
190        $wkb .= pack('L',7);
191        $wkb .= $this->writeMulti($geometry);
192        break;
193    }
194
195    if ($write_as_hex) {
196      $unpacked = unpack('H*',$wkb);
197      return $unpacked[1];
198    }
199    else {
200      return $wkb;
201    }
202  }
203
204  function writePoint($point) {
205    // Set the coords
206    if (!$point->isEmpty()) {
207      $wkb = pack('dd',$point->x(), $point->y());
208      return $wkb;
209    } else {
210      return '';
211    }
212  }
213
214  function writeLineString($line) {
215    // Set the number of points in this line
216    $wkb = pack('L',$line->numPoints());
217
218    // Set the coords
219    foreach ($line->getComponents() as $point) {
220      $wkb .= pack('dd',$point->x(), $point->y());
221    }
222
223    return $wkb;
224  }
225
226  function writePolygon($poly) {
227    // Set the number of lines in this poly
228    $wkb = pack('L',$poly->numGeometries());
229
230    // Write the lines
231    foreach ($poly->getComponents() as $line) {
232      $wkb .= $this->writeLineString($line);
233    }
234
235    return $wkb;
236  }
237
238  function writeMulti($geometry) {
239    // Set the number of components
240    $wkb = pack('L',$geometry->numGeometries());
241
242    // Write the components
243    foreach ($geometry->getComponents() as $component) {
244      $wkb .= $this->write($component);
245    }
246
247    return $wkb;
248  }
249
250}
251