geometry) ? $input->geometry->spatialReference : $input->spatialReference; if (property_exists($input, 'x') && property_exists($input, 'y')) { $coords = array($input->x, $input->y); if (property_exists($input, 'z')) { $coords[] = $input->z; } return $this->arrayToPoint($coords); } if (isset($input->points) && $input->points) { return $this->arrayToMultiPoint($input->points); } if (isset($input->paths) && $input->paths) { if (count($input->paths) === 1) { return $this->arrayToLineString($input->paths[0]); } else { return $this->arrayToMultiLineString($input->paths); } } if (property_exists($input, 'rings')) { return $this->convertRingsToGeometry($input->rings); } if ((isset($input->compressedGeometry) && $input->compressedGeometry) || (isset($input->geometry)) && $input->geometry) { if ($input->compressedGeometry) { $input->geometry = (object) array( 'paths' => array( $this->decompressGeometry($input->compressedGeometry) ), ); } return $this->read($input->geometry); } if ((isset($input->features)) && $input->features) { $geometries = array(); foreach ($input->features as $feature) { $geometries[] = $this->read($feature); } return new GeometryCollection($geometries); } // Should have returned something by now. throw new Exception('Invalid JSON'); } protected function arrayToPoint($array) { return new Point( isset($array[0]) ? $array[0] : NULL, isset($array[1]) ? $array[1] : NULL, isset($array[2]) ? $array[2] : NULL ); } protected function arrayToMultiPoint($array) { $points = array(); foreach ($array as $comp_array) { $points[] = $this->arrayToPoint($comp_array); } return new MultiPoint($points); } protected function arrayToLineString($array) { $points = array(); foreach ($array as $comp_array) { $points[] = $this->arrayToPoint($comp_array); } return new LineString($points); } protected function arrayToPolygon($array) { $lines = array(); foreach ($array as $comp_array) { $lines[] = $this->arrayToLineString($comp_array); } return new Polygon($lines); } protected function arrayToMultiLineString($array) { $lines = array(); foreach ($array as $comp_array) { $lines[] = $this->arrayToLineString($comp_array); } return new MultiLineString($lines); } protected function arrayToMultiPolygon($array) { $polys = array(); foreach ($array as $comp_array) { $polys[] = $this->arrayToPolygon($comp_array); } return new MultiPolygon($polys); } /** * Serializes an object into a geojson string * * * @param Geometry $obj The object to serialize * * @return string The GeoJSON string */ public function write(Geometry $geometry, $return_array = FALSE) { if ($return_array) { return $this->getArray($geometry); } else { return json_encode($this->getArray($geometry)); } } protected function getArray(Geometry $geometry) { $result = array('spatialReference' => (object) array('wkid' => 4326)); switch ($geometry->geometryType()) { case "Point": $result['x'] = $geometry->x(); $result['y'] = $geometry->y(); if ($geometry->hasZ()) { $result['z'] = $geometry->z(); } break; case "MultiPoint": $result['points'] = $geometry->asArray(); break; case "LineString": $result['paths'] = array($geometry->asArray()); break; case "MultiLineString": $result['paths'] = $geometry->asArray(); break; case "Polygon": $result['rings'] = $this->orientRings($geometry->asArray()); break; case "MultiPolygon": $result['rings'] = $this->flattenMultiPolygonRings($geometry->asArray()); break; case "GeometryCollection": $result['features'] = array(); foreach ($geometry->getComponents() as $component) { $result['features'][] = $this->getArray($component); } break; } return $result; } /** * Decompresses compressed geometry. * * * @param string $str The compressed geometry. * @return array The decompressed geometry points. */ protected function decompressGeometry($str) { $xDiffPrev = 0; $yDiffPrev = 0; $points = array(); $strings = array(); // Split the string into an array on the + and - characters $string = preg_match_all('/((\+|\-)[^\+\-]+)/', $str, $strings); // The first value is the coefficient in base 32 $coefficient = intval($strings[0], 32); for ($i = 1; $i < count($strings); $i += 2) { // $i is the offset for the $x value // Convert the value from base 32 and add the previous $x value $x = (intval($strings[$i], 32) + $xDiffPrev); $xDiffPrev = $x; // j+1 is the offset for the y value // Convert the value from base 32 and add the previous y value $y = (intval($strings[i + 1], 32) + $yDiffPrev); $yDiffPrev = $y; array_push($points, array($x / $coefficient, $y / $coefficient)); } return $points; } /** * Checks if the first and last points of a ring are equal and closes the * ring if not. * * * @param array $coordinates The coordinates of the ring. * @return array The coordinates of the closed ring. */ protected function closeRing($coordinates) { if (!$this->pointsEqual($coordinates[0], $coordinates[count($coordinates) - 1])) { array_push($coordinates, $coordinates[0]); } return $coordinates; } /** * Checks if 2 x,y points are equal. * * * @param array $a The coordinates of point a. * @param array $b The coordinates of point b. * @return boolean Whether or not the points are equal. */ protected function pointsEqual($a, $b) { for ($i = 0; $i < count($a); $i++) { if ($a[$i] !== $b[$i]) { return FALSE; } } return TRUE; } /** * Determine if polygon ring coordinates are clockwise. Clockwise signifies * outer ring, counter-clockwise an inner ring or hole. This logic was found * at http://stackoverflow.com/questions/1165647/how-to-determine-if-a-list- * of-polygon-points-are-in-clockwise-order * * * @param array $ringToTest The coordinates of the ring to test. * @return boolean Wether or not the ring is clockwise. */ protected function ringIsClockwise($ringToTest) { $total = 0; $rLength = count($ringToTest); $pt1 = $ringToTest[0]; for ($i = 0; $i < $rLength - 1; $i++) { $pt2 = $ringToTest[$i + 1]; $total += ($pt2[0] - $pt1[0]) * ($pt2[1] + $pt1[1]); $pt1 = $pt2; } return ($total >= 0); } /** * Ensures that rings are oriented in the right directions outer rings are * clockwise, holes are counterclockwise. * * * @param array $polygon The rings to orient. * @return array The oriented rings. */ protected function orientRings($polygon) { $output = array(); $outerRing = $this->closeRing(array_shift($polygon)); if (count($outerRing) >= 4) { if (!$this->ringIsClockwise($outerRing)) { $outerRing = array_reverse($outerRing); } array_push($output, $outerRing); for ($i = 0; $i < count($polygon); $i++) { $hole = $this->closeRing($polygon[$i]); if (count($hole) >= 4) { if ($this->ringIsClockwise($hole)) { $hole = array_reverse($hole); } array_push($output, $hole); } } } return $output; } /** This function flattens holes in multipolygons to one array of polygons. * [ * [ * [ array of outer coordinates ] * [ hole coordinates ] * [ hole coordinates ] * ], * [ * [ array of outer coordinates ] * [ hole coordinates ] * [ hole coordinates ] * ], * ] * becomes * [ * [ array of outer coordinates ] * [ hole coordinates ] * [ hole coordinates ] * [ array of outer coordinates ] * [ hole coordinates ] * [ hole coordinates ] * ] * * * @param array $rings The rings to flatten. * @return array The flattened rings. */ protected function flattenMultiPolygonRings($rings) { $output = array(); for ($i = 0; $i < count($rings); $i++) { $polygon = $this->orientRings($rings[$i]); for ($j = count($polygon) - 1; $j >= 0; $j--) { array_push($output, $polygon[$j]); } } return $output; } /** * Checks if two edges intersect. * * * @param array $a1 First coordinate of the first edge to check. * @param array $a2 Second coordinate of the first edge to check. * @param array $b1 First coordinate of the second edge to check. * @param array $b2 Second coordinate of the second edge to check. * @return boolean Whether or not the edges intersect. */ protected function edgeIntersectsEdge($a1, $a2, $b1, $b2) { $ua_t = ($b2[0] - $b1[0]) * ($a1[1] - $b1[1]) - ($b2[1] - $b1[1]) * ($a1[0] - $b1[0]); $ub_t = ($a2[0] - $a1[0]) * ($a1[1] - $b1[1]) - ($a2[1] - $a1[1]) * ($a1[0] - $b1[0]); $u_b = ($b2[1] - $b1[1]) * ($a2[0] - $a1[0]) - ($b2[0] - $b1[0]) * ($a2[1] - $a1[1]); if ($u_b != 0) { $ua = $ua_t / $u_b; $ub = $ub_t / $u_b; if (0 <= $ua && $ua <= 1 && 0 <= $ub && $ub <= 1) { return TRUE; } } return FALSE; } /** * Checks if an array of coordinates intersect. * * @param array $a The first array of coordinates to check. * @param array $b The second array of coordinates to check. * @return boolean Whether or not the arrays of coordinates intersect. */ protected function arraysIntersectArrays($a, $b) { if (is_numeric($a[0][0])) { if (is_numeric($b[0][0])) { for ($i = 0; $i < count($a) - 1; $i++) { for ($j = 0; $j < count($b) - 1; $j++) { if ($this->edgeIntersectsEdge($a[$i], $a[$i + 1], $b[$j], $b[$j + 1])) { return TRUE; } } } } else { for ($k = 0; $k < count($b); $k++) { if ($this->arraysIntersectArrays($a, $b[$k])) { return TRUE; } } } } else { for ($l = 0; $l < count($a); $l++) { if ($this->arraysIntersectArrays($a[$l], $b)) { return TRUE; } } } return FALSE; } /** * Check if an array of coordinates contain a point. * * * @param array $coordinates The array of coordinates to check. * @param array $point The coordinates of the point to check. * @return boolean Whether or not the array of coordinates contain the point. */ protected function coordinatesContainPoint($coordinates, $point) { $contains = FALSE; for ($i = -1, $l = count($coordinates), $j = $l - 1; ++$i < $l; $j = $i) { if ((($coordinates[$i][1] <= $point[1] && $point[1] < $coordinates[$j][1]) || ($coordinates[$j][1] <= $point[1] && $point[1] < $coordinates[$i][1])) && ($point[0] < ($coordinates[$j][0] - $coordinates[$i][0]) * ($point[1] - $coordinates[$i][1]) / ($coordinates[$j][1] - $coordinates[$i][1]) + $coordinates[$i][0])) { $contains = !$contains; } } return $contains; } /** * Checks if an array of coordinates contains an other array of coordinates. * * * @param array $outer The array of outer coordinates. * @param array $inner The array of inner coordinates. * @return boolean Whether or not the outer array contains the inner array. */ protected function coordinatesContainCoordinates($outer, $inner) { $intersects = $this->arraysIntersectArrays($outer, $inner); $contains = $this->coordinatesContainPoint($outer, $inner[0]); if (!$intersects && $contains) { return TRUE; } return FALSE; } /** * Checks if any polygons in this array contain any other polygons in this * array. Used for checking holes in arcgis rings. * * * @param array $rings The argcis rings to check. * @return Geometry The converted rings. */ protected function convertRingsToGeometry($rings) { $outerRings = array(); $holes = array(); // For each ring. for ($r = 0; $r < count($rings); $r++) { $ring = $this->closeRing($rings[$r]); if (count($ring) < 4) { continue; } // Is this ring an outer ring? Is it clockwise? if ($this->ringIsClockwise($ring)) { $polygon = array($ring); // Push to outer rings. array_push($outerRings, $polygon); } else { // Counterclockwise push to holes. array_push($holes, $ring); } } $uncontainedHoles = array(); // While there are holes left... while (count($holes)) { // Pop a hole off out stack. $hole = array_pop($holes); // Loop over all outer rings and see if they contain our hole. $contained = FALSE; for ($x = count($outerRings) - 1; $x >= 0; $x--) { $outerRing = $outerRings[$x][0]; if ($this->coordinatesContainCoordinates($outerRing, $hole)) { // The hole is contained push it into our polygon. array_push($outerRings[$x], $hole); $contained = TRUE; break; } } // The ring is not contained in any outer ring. // Sometimes this happens https://github.com/Esri/esri-leaflet/issues/320 if (!$contained) { array_push($uncontainedHoles, $hole); } } // If we couldn't match any holes using contains we can now try intersects... while (count($uncontainedHoles)) { // Pop a hole off out stack. $hole = array_pop($uncontainedHoles); // Loop over all outer rings and see if any intersect our hole. $intersects = FALSE; for ($x = count($outerRings) - 1; $x >= 0; $x--) { $outerRing = $outerRings[$x][0]; if ($this->arraysIntersectArrays($outerRing, $hole)) { // The hole intersects the outer ring push it into our polygon. array_push($outerRings[$x], $hole); $intersects = TRUE; break; } } // Hole does not intersect ANY outer ring at this point. // Make it an outer ring. if (!$intersects) { array_push($outerRings, array(array_reverse(hole))); } } if (count($outerRings) === 1) { return $this->arrayToPolygon($outerRings[0]); } else { return $this->arrayToMultiPolygon($outerRings); } } }