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