1<?php
2/**
3 * WKT (Well Known Text) Adapter
4 */
5class WKT extends GeoAdapter
6{
7
8  /**
9   * Read WKT string into geometry objects
10   *
11   * @param string $WKT A WKT string
12   *
13   * @return Geometry
14   */
15  public function read($wkt) {
16    $wkt = trim($wkt);
17
18    // If it contains a ';', then it contains additional SRID data
19    if (strpos($wkt,';')) {
20      $parts = explode(';', $wkt);
21      $wkt = $parts[1];
22      $eparts = explode('=',$parts[0]);
23      $srid = $eparts[1];
24    }
25    else {
26      $srid = NULL;
27    }
28
29    // If geos is installed, then we take a shortcut and let it parse the WKT
30    if (geoPHP::geosInstalled()) {
31      $reader = new GEOSWKTReader();
32      if ($srid) {
33        $geom = geoPHP::geosToGeometry($reader->read($wkt));
34        $geom->setSRID($srid);
35        return $geom;
36      }
37      else {
38        return geoPHP::geosToGeometry($reader->read($wkt));
39      }
40    }
41    $wkt = str_replace(', ', ',', $wkt);
42
43    // For each geometry type, check to see if we have a match at the
44    // beginning of the string. If we do, then parse using that type
45    foreach (geoPHP::geometryList() as $geom_type) {
46      $wkt_geom = strtoupper($geom_type);
47      if (strtoupper(substr($wkt, 0, strlen($wkt_geom))) == $wkt_geom) {
48        $data_string = $this->getDataString($wkt);
49        $method = 'parse'.$geom_type;
50
51        if ($srid) {
52          $geom = $this->$method($data_string);
53          $geom->setSRID($srid);
54          return $geom;
55        }
56        else {
57          return $this->$method($data_string);
58        }
59
60      }
61    }
62  }
63
64  private function parsePoint($data_string) {
65    $data_string = $this->trimParens($data_string);
66
67    // If it's marked as empty, then return an empty point
68    if ($data_string == 'EMPTY') return new Point();
69
70    $parts = explode(' ',$data_string);
71    return new Point($parts[0], $parts[1]);
72  }
73
74  private function parseLineString($data_string) {
75    $data_string = $this->trimParens($data_string);
76
77    // If it's marked as empty, then return an empty line
78    if ($data_string == 'EMPTY') return new LineString();
79
80    $parts = explode(',',$data_string);
81    $points = array();
82    foreach ($parts as $part) {
83      $points[] = $this->parsePoint($part);
84    }
85    return new LineString($points);
86  }
87
88  private function parsePolygon($data_string) {
89    $data_string = $this->trimParens($data_string);
90
91    // If it's marked as empty, then return an empty polygon
92    if ($data_string == 'EMPTY') return new Polygon();
93
94    $parts = explode('),(',$data_string);
95    $lines = array();
96    foreach ($parts as $part) {
97      if (!$this->beginsWith($part,'(')) $part = '(' . $part;
98      if (!$this->endsWith($part,')'))   $part = $part . ')';
99      $lines[] = $this->parseLineString($part);
100    }
101    return new Polygon($lines);
102  }
103
104  private function parseMultiPoint($data_string) {
105    $data_string = $this->trimParens($data_string);
106
107    // If it's marked as empty, then return an empty MutiPoint
108    if ($data_string == 'EMPTY') return new MultiPoint();
109
110    $parts = explode(',',$data_string);
111    $points = array();
112    foreach ($parts as $part) {
113      $points[] = $this->parsePoint($part);
114    }
115    return new MultiPoint($points);
116  }
117
118  private function parseMultiLineString($data_string) {
119    $data_string = $this->trimParens($data_string);
120
121    // If it's marked as empty, then return an empty multi-linestring
122    if ($data_string == 'EMPTY') return new MultiLineString();
123
124    $parts = explode('),(',$data_string);
125    $lines = array();
126    foreach ($parts as $part) {
127      // Repair the string if the explode broke it
128      if (!$this->beginsWith($part,'(')) $part = '(' . $part;
129      if (!$this->endsWith($part,')'))   $part = $part . ')';
130      $lines[] = $this->parseLineString($part);
131    }
132    return new MultiLineString($lines);
133  }
134
135  private function parseMultiPolygon($data_string) {
136    $data_string = $this->trimParens($data_string);
137
138    // If it's marked as empty, then return an empty multi-polygon
139    if ($data_string == 'EMPTY') return new MultiPolygon();
140
141    $parts = explode(')),((',$data_string);
142    $polys = array();
143    foreach ($parts as $part) {
144      // Repair the string if the explode broke it
145      if (!$this->beginsWith($part,'((')) $part = '((' . $part;
146      if (!$this->endsWith($part,'))'))   $part = $part . '))';
147      $polys[] = $this->parsePolygon($part);
148    }
149    return new MultiPolygon($polys);
150  }
151
152  private function parseGeometryCollection($data_string) {
153    $data_string = $this->trimParens($data_string);
154
155    // If it's marked as empty, then return an empty geom-collection
156    if ($data_string == 'EMPTY') return new GeometryCollection();
157
158    $geometries = array();
159    $matches = array();
160    $str = preg_replace('/,\s*([A-Za-z])/', '|$1', $data_string);
161    $components = explode('|', trim($str));
162
163    foreach ($components as $component) {
164      $geometries[] = $this->read($component);
165    }
166    return new GeometryCollection($geometries);
167  }
168
169  protected function getDataString($wkt) {
170    $first_paren = strpos($wkt, '(');
171
172    if ($first_paren !== FALSE) {
173      return substr($wkt, $first_paren);
174    } elseif (strstr($wkt,'EMPTY')) {
175      return 'EMPTY';
176    } else
177      return FALSE;
178  }
179
180  /**
181   * Trim the parenthesis and spaces
182   */
183  protected function trimParens($str) {
184    $str = trim($str);
185
186    // We want to only strip off one set of parenthesis
187    if ($this->beginsWith($str, '(')) {
188      return substr($str,1,-1);
189    }
190    else return $str;
191  }
192
193  protected function beginsWith($str, $char) {
194    if (substr($str,0,strlen($char)) == $char) return TRUE;
195    else return FALSE;
196  }
197
198  protected function endsWith($str, $char) {
199    if (substr($str,(0 - strlen($char))) == $char) return TRUE;
200    else return FALSE;
201  }
202
203  /**
204   * Serialize geometries into a WKT string.
205   *
206   * @param Geometry $geometry
207   *
208   * @return string The WKT string representation of the input geometries
209   */
210  public function write(Geometry $geometry) {
211    // If geos is installed, then we take a shortcut and let it write the WKT
212    if (geoPHP::geosInstalled()) {
213      $writer = new GEOSWKTWriter();
214      $writer->setTrim(TRUE);
215      return $writer->write($geometry->geos());
216    }
217
218    if ($geometry->isEmpty()) {
219      return strtoupper($geometry->geometryType()).' EMPTY';
220    }
221    else if ($data = $this->extractData($geometry)) {
222      return strtoupper($geometry->geometryType()).' ('.$data.')';
223    }
224  }
225
226  /**
227   * Extract geometry to a WKT string
228   *
229   * @param Geometry $geometry A Geometry object
230   *
231   * @return string
232   */
233  public function extractData($geometry) {
234    $parts = array();
235    switch ($geometry->geometryType()) {
236      case 'Point':
237        return $geometry->getX().' '.$geometry->getY();
238      case 'LineString':
239        foreach ($geometry->getComponents() as $component) {
240          $parts[] = $this->extractData($component);
241        }
242        return implode(', ', $parts);
243      case 'Polygon':
244      case 'MultiPoint':
245      case 'MultiLineString':
246      case 'MultiPolygon':
247        foreach ($geometry->getComponents() as $component) {
248          $parts[] = '('.$this->extractData($component).')';
249        }
250        return implode(', ', $parts);
251      case 'GeometryCollection':
252        foreach ($geometry->getComponents() as $component) {
253          $parts[] = strtoupper($component->geometryType()).' ('.$this->extractData($component).')';
254        }
255        return implode(', ', $parts);
256    }
257  }
258}
259