1<?php 2 3namespace geoPHP\Adapter; 4 5use geoPHP\geoPHP; 6use geoPHP\Geometry\Geometry; 7use geoPHP\Geometry\GeometryCollection; 8use geoPHP\Geometry\Point; 9use geoPHP\Geometry\MultiPoint; 10use geoPHP\Geometry\LineString; 11use geoPHP\Geometry\MultiLineString; 12use geoPHP\Geometry\Polygon; 13use geoPHP\Geometry\MultiPolygon; 14 15/** 16 * GeoJSON class : a geoJSON reader/writer. 17 * 18 * Note that it will always return a GeoJSON geometry. This 19 * means that if you pass it a feature, it will return the 20 * geometry of that feature strip everything else. 21 */ 22class GeoJSON implements GeoAdapter 23{ 24 /** 25 * Given an object or a string, return a Geometry 26 * 27 * @param string|object $input The GeoJSON string or object 28 * @return Geometry 29 * @throws \Exception 30 */ 31 public function read($input) 32 { 33 if (is_string($input)) { 34 $input = json_decode($input); 35 } 36 if (!is_object($input)) { 37 throw new \Exception('Invalid JSON'); 38 } 39 if (!isset($input->type) || !is_string($input->type)) { 40 throw new \Exception('Invalid GeoJSON'); 41 } 42 43 // Check to see if it's a FeatureCollection 44 if ($input->type == 'FeatureCollection' && isset($input->features)) { 45 $geometries = []; 46 foreach ($input->features as $feature) { 47 $geometries[] = $this->read($feature); 48 } 49 return geoPHP::buildGeometry($geometries); 50 } 51 52 // Check to see if it's a Feature 53 if ($input->type == 'Feature') { 54 return $this->geoJSONFeatureToGeometry($input); 55 } 56 57 // It's a geometry - process it 58 return $this->geoJSONObjectToGeometry($input); 59 } 60 61 /** 62 * @param object $input 63 * @return string|null 64 */ 65 private function getSRID($input) 66 { 67 if (isset($input->crs->properties->name)) { 68 // parse CRS codes in forms "EPSG:1234" and "urn:ogc:def:crs:EPSG::1234" 69 preg_match('#EPSG[:]+(\d+)#', $input->crs->properties->name, $m); 70 return isset($m[1]) ? $m[1] : null; 71 } 72 return null; 73 } 74 75 /** 76 * @param object $obj 77 * @return Geometry 78 * @throws \Exception 79 */ 80 private function geoJSONFeatureToGeometry($obj) 81 { 82 $geometry = $this->read($obj->geometry); 83 if (isset($obj->properties)) { 84 foreach ($obj->properties as $property => $value) { 85 $geometry->setData($property, $value); 86 } 87 } 88 89 return $geometry; 90 } 91 92 /** 93 * @param object $obj 94 * @return Geometry 95 * @throws \Exception 96 */ 97 private function geoJSONObjectToGeometry($obj) 98 { 99 $type = $obj->type; 100 101 if ($type == 'GeometryCollection') { 102 return $this->geoJSONObjectToGeometryCollection($obj); 103 } 104 $method = 'arrayTo' . $type; 105 /** @var GeometryCollection $geometry */ 106 $geometry = $this->$method($obj->coordinates); 107 $geometry->setSRID($this->getSRID($obj)); 108 return $geometry; 109 } 110 111 /** 112 * @param array $coordinates Array of coordinates 113 * @return Point 114 */ 115 private function arrayToPoint($coordinates) 116 { 117 switch (count($coordinates)) { 118 case 2: 119 return new Point($coordinates[0], $coordinates[1]); 120 break; 121 case 3: 122 return new Point($coordinates[0], $coordinates[1], $coordinates[2]); 123 break; 124 case 4: 125 return new Point($coordinates[0], $coordinates[1], $coordinates[2], $coordinates[3]); 126 break; 127 default: 128 return new Point(); 129 } 130 } 131 132 private function arrayToLineString($array) 133 { 134 $points = []; 135 foreach ($array as $componentArray) { 136 $points[] = $this->arrayToPoint($componentArray); 137 } 138 return new LineString($points); 139 } 140 141 private function arrayToPolygon($array) 142 { 143 $lines = []; 144 foreach ($array as $componentArray) { 145 $lines[] = $this->arrayToLineString($componentArray); 146 } 147 return new Polygon($lines); 148 } 149 150 /** @noinspection PhpUnusedPrivateMethodInspection */ 151 /** 152 * @param array $array 153 * @return MultiPoint 154 */ 155 private function arrayToMultiPoint($array) 156 { 157 $points = []; 158 foreach ($array as $componentArray) { 159 $points[] = $this->arrayToPoint($componentArray); 160 } 161 return new MultiPoint($points); 162 } 163 164 /** @noinspection PhpUnusedPrivateMethodInspection */ 165 /** 166 * @param array $array 167 * @return MultiLineString 168 */ 169 private function arrayToMultiLineString($array) 170 { 171 $lines = []; 172 foreach ($array as $componentArray) { 173 $lines[] = $this->arrayToLineString($componentArray); 174 } 175 return new MultiLineString($lines); 176 } 177 178 /** @noinspection PhpUnusedPrivateMethodInspection */ 179 /** 180 * @param array $array 181 * @return MultiPolygon 182 */ 183 private function arrayToMultiPolygon($array) 184 { 185 $polygons = []; 186 foreach ($array as $componentArray) { 187 $polygons[] = $this->arrayToPolygon($componentArray); 188 } 189 return new MultiPolygon($polygons); 190 } 191 192 /** 193 * @param object $obj 194 * @throws \Exception 195 * @return GeometryCollection 196 */ 197 private function geoJSONObjectToGeometryCollection($obj) 198 { 199 $geometries = []; 200 if (!property_exists($obj, 'geometries')) { 201 throw new \Exception('Invalid GeoJSON: GeometryCollection with no component geometries'); 202 } 203 foreach ($obj->geometries ?: [] as $componentObject) { 204 $geometries[] = $this->geoJSONObjectToGeometry($componentObject); 205 } 206 $collection = new GeometryCollection($geometries); 207 $collection->setSRID($this->getSRID($obj)); 208 return $collection; 209 } 210 211 /** 212 * Serializes an object into a geojson string 213 * 214 * 215 * @param Geometry $geometry The object to serialize 216 * @param boolean $returnAsArray 217 * 218 * @return string|array The GeoJSON string 219 */ 220 public function write(Geometry $geometry, $returnAsArray = false) 221 { 222 return $returnAsArray 223 ? $this->getArray($geometry) 224 : json_encode($this->getArray($geometry)); 225 } 226 227 228 229 /** 230 * Creates a geoJSON array 231 * 232 * If the root geometry is a GeometryCollection, and any of its geometries has data, 233 * the root element will be a FeatureCollection with Feature elements (with the data) 234 * If the root geometry has data, it will be included in a Feature object that contains the data 235 * 236 * The geometry should have geographical coordinates since CRS support has been removed from from geoJSON specification (RFC 7946) 237 * The geometry should'nt be measured, since geoJSON specification (RFC 7946) only supports the dimensional positions 238 * 239 * @param Geometry|GeometryCollection $geometry 240 * @param bool|null $isRoot Is geometry the root geometry? 241 * @return array 242 */ 243 public function getArray($geometry, $isRoot = true) 244 { 245 if ($geometry->geometryType() === Geometry::GEOMETRY_COLLECTION) { 246 $components = []; 247 $isFeatureCollection = false; 248 foreach ($geometry->getComponents() as $component) { 249 if ($component->getData() !== null) { 250 $isFeatureCollection = true; 251 } 252 $components[] = $this->getArray($component, false); 253 } 254 if (!$isFeatureCollection || !$isRoot) { 255 return [ 256 'type' => 'GeometryCollection', 257 'geometries' => $components 258 ]; 259 } else { 260 $features = []; 261 foreach ($geometry->getComponents() as $i => $component) { 262 $features[] = [ 263 'type' => 'Feature', 264 'properties' => $component->getData(), 265 'geometry' => $components[$i], 266 ]; 267 } 268 return [ 269 'type' => 'FeatureCollection', 270 'features' => $features 271 ]; 272 } 273 } 274 275 if ($isRoot && $geometry->getData() !== null) { 276 return [ 277 'type' => 'Feature', 278 'properties' => $geometry->getData(), 279 'geometry' => [ 280 'type' => $geometry->geometryType(), 281 'coordinates' => $geometry->isEmpty() ? [] : $geometry->asArray() 282 ] 283 ]; 284 } 285 $object = [ 286 'type' => $geometry->geometryType(), 287 'coordinates' => $geometry->isEmpty() ? [] : $geometry->asArray() 288 ]; 289 return $object; 290 } 291} 292