Geometry.php 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. <?php
  2. class GeometryPoint {
  3. public $x, $y;
  4. public function __construct($x, $y) {
  5. if (!(is_numeric($x) && is_numeric($y))) throw new Exception('Bad arguments');
  6. $this->x = (float)$x;
  7. $this->y = (float)$y;
  8. }
  9. public static function same(GeometryPoint $a, GeometryPoint $b) {
  10. return ($a->x == $b->x && $a->y == $b->y);
  11. }
  12. }
  13. class GeometryLine {
  14. public $a, $b;
  15. public function __construct(GeometryPoint $a, GeometryPoint $b) {
  16. $this->a = $a;
  17. $this->b = $b;
  18. }
  19. public function length() {
  20. return Geometry::distance($this->a, $this->b);
  21. }
  22. public function asText($reproject = null) {
  23. if ($reproject) list($a, $b) = GeometryObject::reproject([$this->a, $this->b], $reproject);
  24. else list($a, $b) = [$this->a, $this->b];
  25. return "LINESTRING({$a->x} {$a->y},{$b->x} {$b->y})";
  26. }
  27. }
  28. class GeometryPolygon {
  29. protected $points;
  30. public function __construct(GeometryPoint ...$points) {
  31. if (reset($points)->x != end($points)->x || reset($points)->y != end($points)->y) $points[] = reset($points);
  32. $this->points = $points;
  33. }
  34. public function asText($reproject = null) {
  35. if ($reproject) $points = GeometryObject::reproject($this->points, $reproject);
  36. else $points = $this->points;
  37. return 'POLYGON((' . implode(',', array_map(function ($point) {
  38. return "{$point->x} {$point->y}";
  39. }, $points)) . '))';
  40. }
  41. }
  42. class GeometryObject {
  43. protected $type, $points;
  44. public function type() { return $this->type; }
  45. public function numPoints() { return count($this->points); }
  46. public function points() { return $this->points; }
  47. public function __construct($type, $points) {
  48. $type = (string)$type;
  49. if (!($type && is_array($points))) throw new Exception(__CLASS__ . "::" . __FUNCTION__ . ' - bad argument');
  50. foreach ($points as $point) if (get_class($point) != 'GeometryPoint') throw new Exception(__CLASS__ . "::" . __FUNCTION__ . ' - bad argument');
  51. $this->type = $type;
  52. $this->points = $points;
  53. }
  54. public static function getFromText($text, $reproject = null) {
  55. if (!preg_match('/^([[:alpha:]]+)\((.*)\)$/', $text, $matches)) throw new Exception('Invalid WKT syntax');
  56. $type = (string)$matches[1];
  57. $points = array_map(function ($point) {
  58. list($x, $y) = explode(' ', $point, 2);
  59. return new GeometryPoint($x, $y);
  60. }, explode(',', $matches[2]));
  61. if ($reproject) $points = self::reproject($points, $reproject);
  62. return new GeometryObject($type, $points);
  63. }
  64. public static function reproject($points, $reproject) {
  65. if (!is_array($points)) throw new Exception(__CLASS__ . "::" . __FUNCTION__ . ' - bad argument #1');
  66. Lib::loadClass('EpsgConversion');
  67. if (!method_exists('EpsgConversion', $reproject)) throw new Exception("Function EpsgConversion::{$reproject} not exists");
  68. return array_map(function ($point) use ($reproject) {
  69. if (get_class($point) != 'GeometryPoint') throw new Exception(__CLASS__ . "::" . __FUNCTION__ . ' - bad argument #2');
  70. $_point = EpsgConversion::$reproject($point->x, $point->y);
  71. return new GeometryPoint($_point->x, $_point->y);
  72. }, $points);
  73. }
  74. }
  75. class GeometryLinearFunction {
  76. protected $a, $b, $x;
  77. public function __construct($a = null, $b = null, $x = null) {
  78. if (($a === null && $b !== null) || ($a !== null && $b === null) || ($a === null && $x === null) || ($a !== null && $x !== null)) throw new Exception('Bad arguments');
  79. $this->a = $a; $this->b = $b; $this->x = $x;
  80. }
  81. public function valueOfX($x) {
  82. if ($this->x !== null) return null;
  83. return $this->a * $x + $this->b;
  84. }
  85. public function valueOfY($y) {
  86. if ($this->x !== null) return $x;
  87. if ($this->a == 0) return null;
  88. return ($y - $this->b) / $this->a;
  89. }
  90. public static function getFromLine(GeometryLine $L) {
  91. $a = null; $b = null; $x = null;
  92. if ($L->a->x == $L->b->x) {
  93. $x = $L->a->x;
  94. } else {
  95. $a = ($L->a->y - $L->b->y) / ($L->a->x - $L->b->x);
  96. $b = $L->b->y - $a * $L->b->x;
  97. }
  98. return new GeometryLinearFunction($a, $b, $x);
  99. }
  100. public static function findCrossPoint(GeometryLinearFunction $f1, GeometryLinearFunction $f2) {
  101. if ($f1->x !== null) {
  102. if ($f2->x !== null) {
  103. if ($f1->x == $f2->x) return $f1;
  104. return null;
  105. }
  106. $x = $f1->x;
  107. $y = $f2->a * $x + $f2->b;
  108. } elseif ($f2->x !== null) {
  109. $x = $f2->x;
  110. $y = $f1->a * $x + $f1->b;
  111. } else {
  112. if ($f1->a == $f2->a) {
  113. if ($f1->b == $f2->b) return $f1;
  114. return null;
  115. }
  116. $x = ($f2->b - $f1->b) / ($f1->a - $f2->a);
  117. $y = $f1->a * $x + $f1->b;
  118. }
  119. return new GeometryPoint($x, $y);
  120. }
  121. public static function functionPerpendicularAtPoint(GeometryLinearFunction $f, GeometryPoint $p) {
  122. $a = null; $b = null; $x = null;
  123. if ($f->x !== null) {
  124. $a = 0;
  125. $b = $p->y;
  126. } elseif ($f->a == 0) {
  127. $x = $p->x;
  128. } else {
  129. $a = -1 * (1 / $f->a);
  130. $b = $p->y - $a * $p->x;
  131. }
  132. return new GeometryLinearFunction($a, $b, $x);
  133. }
  134. public static function pointsDistanced(GeometryLinearFunction $f, GeometryPoint $p, $distance) {
  135. if (!is_numeric($distance)) throw new Exception('Bad argument');
  136. $fpap = self::functionPerpendicularAtPoint($f, $p);
  137. if ($fpap->x !== null) {
  138. return [new GeometryPoint($p->x, $p->y - 1), new GeometryPoint($p->x, $p->y + 1)];
  139. } else {
  140. $d = $distance / sqrt(1 + pow($fpap->a, 2));
  141. $x1 = $p->x - $d;
  142. $x2 = $p->x + $d;
  143. $y1 = $fpap->valueOfX($x1);
  144. $y2 = $fpap->valueOfX($x2);
  145. return [new GeometryPoint($x1, $y1), new GeometryPoint($x2, $y2)];
  146. }
  147. }
  148. }
  149. class Geometry {
  150. public static function point($x, $y) { return new GeometryPoint($x, $y); }
  151. public static function line(GeometryPoint $a, GeometryPoint $b) { return new GeometryLine($a, $b); }
  152. public static function objectFromText($text, $reproject = null) { return GeometryObject::getFromText($text, $reproject); }
  153. public static function samePoint(GeometryPoint $a, GeometryPoint $b) { return GeometryPoint::same($a, $b); }
  154. public static function pointsToLines($points) {
  155. if (!is_array($points)) throw new Exception(__CLASS__ . "::" . __FUNCTION__ . ' - bad argument');
  156. $lines = [];
  157. $lastPoint = null;
  158. foreach ($points as $point) {
  159. if (get_class($point) != 'GeometryPoint') throw new Exception(__CLASS__ . "::" . __FUNCTION__ . ' - bad argument');
  160. if ($lastPoint) $lines[] = self::line($lastPoint, $point);
  161. $lastPoint = $point;
  162. }
  163. return $lines;
  164. }
  165. public static function closedPointOnLine(GeometryPoint $p, GeometryLine $L) {
  166. // Funkcja zwraca najbliży punkt należący do odcinka L względem punktu p
  167. $f1 = GeometryLinearFunction::getFromLine($L);
  168. $f2 = GeometryLinearFunction::functionPerpendicularAtPoint($f1, $p);
  169. $crossPoint = GeometryLinearFunction::findCrossPoint($f1, $f2);
  170. if (self::distance($crossPoint, $L->a) <= $L->length() && self::distance($crossPoint, $L->b) <= $L->length()) return $crossPoint;
  171. if (self::distance($crossPoint, $L->a) < self::distance($crossPoint, $L->b)) return $L->a;
  172. return $L->b;
  173. }
  174. public static function distance($a, $b) {
  175. if (!(is_object($a) && is_object($b))) throw new Exception(__CLASS__ . "::" . __FUNCTION__ . ' - bad argument');
  176. if (get_class($a) == 'GeometryPoint' && get_class($b) == 'GeometryPoint') return round(sqrt(pow($a->x - $b->x, 2) + pow($a->y - $b->y, 2)), 7);
  177. if (get_class($a) == 'GeometryPoint' && get_class($b) == 'GeometryLine') return self::distance($a, self::closedPointOnLine($a, $b));
  178. if (get_class($a) == 'GeometryLine' && get_class($b) == 'GeometryPoint') return self::distance($b, $a);
  179. if (get_class($a) == 'GeometryLine' && get_class($b) == 'GeometryLine') {
  180. if (self::crossPoint($a, $b)) return 0;
  181. return min(self::distance($a, $b->a), self::distance($a, $b->b), self::distance($a->a, $b), self::distance($a->b, $b));
  182. }
  183. throw new Exception(__CLASS__ . "::" . __FUNCTION__ . ' - object not implemented');
  184. }
  185. public static function crossPoint(GeometryLine $A, GeometryLine $B) {
  186. if (!($p = GeometryLinearFunction::findCrossPoint(GeometryLinearFunction::getFromLine($A), GeometryLinearFunction::getFromLine($B)))) return null;
  187. switch (get_class($p)) {
  188. case 'GeometryPoint':
  189. if (self::distance($p, $A) == 0 && self::distance($p, $B) == 0) return [$p];
  190. // if ($p->x >= min($A->a->x, $A->b->x) && $p->x <= max($A->a->x, $A->b->x) && $p->y >= min($A->a->y, $A->b->y) && $p->y <= max($A->a->y, $A->b->y) &&
  191. // $p->x >= min($B->a->x, $B->b->x) && $p->x <= max($B->a->x, $B->b->x) && $p->y >= min($B->a->y, $B->b->y) && $p->y <= max($B->a->y, $B->b->y)) return [$p];
  192. break;
  193. case 'GeometryLinearFunction':
  194. if ($A->a->x > $A->b->x) {
  195. $tmp = $A->a; $A->a = $A->b; $A->b = $tmp;
  196. $reverse = true;
  197. } else $reverse = false;
  198. if ($B->a->x > $B->b->x) {
  199. $tmp = $B->a; $B->a = $B->b; $B->b = $tmp;
  200. }
  201. if ($A->a->x > $B->b->x || $A->b->x < $B->a->x) return null;
  202. if ($A->a->x > $B->a->x) $a = $A->a; else $a = $B->a;
  203. if ($A->b->x < $B->b->x) $b = $A->b; else $b = $B->b;
  204. if ($a->x == $b->x) return [$a]; elseif ($reverse) return [$b, $a]; else return [$a, $b];
  205. default: throw new Exception('Uknown error');
  206. }
  207. return null;
  208. }
  209. public static function lineToRectangle(GeometryLine $L, $size) {
  210. $f = GeometryLinearFunction::getFromLine($L);
  211. $p1 = GeometryLinearFunction::pointsDistanced($f, $L->a, $size);
  212. $p2 = GeometryLinearFunction::pointsDistanced($f, $L->b, $size);
  213. return new GeometryPolygon($p1[0], $p2[0], $p2[1], $p1[1]);
  214. }
  215. }