GisGeometryCollection.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. <?php
  2. /**
  3. * Handles actions related to GIS GEOMETRYCOLLECTION objects
  4. */
  5. declare(strict_types=1);
  6. namespace PhpMyAdmin\Gis;
  7. use TCPDF;
  8. use function array_merge;
  9. use function count;
  10. use function mb_strlen;
  11. use function mb_strpos;
  12. use function mb_substr;
  13. use function str_split;
  14. /**
  15. * Handles actions related to GIS GEOMETRYCOLLECTION objects
  16. */
  17. class GisGeometryCollection extends GisGeometry
  18. {
  19. /** @var self */
  20. private static $instance;
  21. /**
  22. * A private constructor; prevents direct creation of object.
  23. *
  24. * @access private
  25. */
  26. private function __construct()
  27. {
  28. }
  29. /**
  30. * Returns the singleton.
  31. *
  32. * @return GisGeometryCollection the singleton
  33. *
  34. * @access public
  35. */
  36. public static function singleton()
  37. {
  38. if (! isset(self::$instance)) {
  39. self::$instance = new GisGeometryCollection();
  40. }
  41. return self::$instance;
  42. }
  43. /**
  44. * Scales each row.
  45. *
  46. * @param string $spatial spatial data of a row
  47. *
  48. * @return array array containing the min, max values for x and y coordinates
  49. *
  50. * @access public
  51. */
  52. public function scaleRow($spatial)
  53. {
  54. $min_max = [];
  55. // Trim to remove leading 'GEOMETRYCOLLECTION(' and trailing ')'
  56. $goem_col
  57. = mb_substr(
  58. $spatial,
  59. 19,
  60. mb_strlen($spatial) - 20
  61. );
  62. // Split the geometry collection object to get its constituents.
  63. $sub_parts = $this->explodeGeomCol($goem_col);
  64. foreach ($sub_parts as $sub_part) {
  65. $type_pos = mb_strpos($sub_part, '(');
  66. if ($type_pos === false) {
  67. continue;
  68. }
  69. $type = mb_substr($sub_part, 0, $type_pos);
  70. $gis_obj = GisFactory::factory($type);
  71. if (! $gis_obj) {
  72. continue;
  73. }
  74. $scale_data = $gis_obj->scaleRow($sub_part);
  75. // Update minimum/maximum values for x and y coordinates.
  76. $c_maxX = (float) $scale_data['maxX'];
  77. if (! isset($min_max['maxX']) || $c_maxX > $min_max['maxX']) {
  78. $min_max['maxX'] = $c_maxX;
  79. }
  80. $c_minX = (float) $scale_data['minX'];
  81. if (! isset($min_max['minX']) || $c_minX < $min_max['minX']) {
  82. $min_max['minX'] = $c_minX;
  83. }
  84. $c_maxY = (float) $scale_data['maxY'];
  85. if (! isset($min_max['maxY']) || $c_maxY > $min_max['maxY']) {
  86. $min_max['maxY'] = $c_maxY;
  87. }
  88. $c_minY = (float) $scale_data['minY'];
  89. if (isset($min_max['minY']) && $c_minY >= $min_max['minY']) {
  90. continue;
  91. }
  92. $min_max['minY'] = $c_minY;
  93. }
  94. return $min_max;
  95. }
  96. /**
  97. * Adds to the PNG image object, the data related to a row in the GIS dataset.
  98. *
  99. * @param string $spatial GIS POLYGON object
  100. * @param string|null $label Label for the GIS POLYGON object
  101. * @param string $color Color for the GIS POLYGON object
  102. * @param array $scale_data Array containing data related to scaling
  103. * @param resource $image Image object
  104. *
  105. * @return resource the modified image object
  106. *
  107. * @access public
  108. */
  109. public function prepareRowAsPng($spatial, ?string $label, $color, array $scale_data, $image)
  110. {
  111. // Trim to remove leading 'GEOMETRYCOLLECTION(' and trailing ')'
  112. $goem_col
  113. = mb_substr(
  114. $spatial,
  115. 19,
  116. mb_strlen($spatial) - 20
  117. );
  118. // Split the geometry collection object to get its constituents.
  119. $sub_parts = $this->explodeGeomCol($goem_col);
  120. foreach ($sub_parts as $sub_part) {
  121. $type_pos = mb_strpos($sub_part, '(');
  122. if ($type_pos === false) {
  123. continue;
  124. }
  125. $type = mb_substr($sub_part, 0, $type_pos);
  126. $gis_obj = GisFactory::factory($type);
  127. if (! $gis_obj) {
  128. continue;
  129. }
  130. $image = $gis_obj->prepareRowAsPng(
  131. $sub_part,
  132. $label,
  133. $color,
  134. $scale_data,
  135. $image
  136. );
  137. }
  138. return $image;
  139. }
  140. /**
  141. * Adds to the TCPDF instance, the data related to a row in the GIS dataset.
  142. *
  143. * @param string $spatial GIS GEOMETRYCOLLECTION object
  144. * @param string|null $label label for the GIS GEOMETRYCOLLECTION object
  145. * @param string $color color for the GIS GEOMETRYCOLLECTION object
  146. * @param array $scale_data array containing data related to scaling
  147. * @param TCPDF $pdf TCPDF instance
  148. *
  149. * @return TCPDF the modified TCPDF instance
  150. *
  151. * @access public
  152. */
  153. public function prepareRowAsPdf($spatial, ?string $label, $color, array $scale_data, $pdf)
  154. {
  155. // Trim to remove leading 'GEOMETRYCOLLECTION(' and trailing ')'
  156. $goem_col
  157. = mb_substr(
  158. $spatial,
  159. 19,
  160. mb_strlen($spatial) - 20
  161. );
  162. // Split the geometry collection object to get its constituents.
  163. $sub_parts = $this->explodeGeomCol($goem_col);
  164. foreach ($sub_parts as $sub_part) {
  165. $type_pos = mb_strpos($sub_part, '(');
  166. if ($type_pos === false) {
  167. continue;
  168. }
  169. $type = mb_substr($sub_part, 0, $type_pos);
  170. $gis_obj = GisFactory::factory($type);
  171. if (! $gis_obj) {
  172. continue;
  173. }
  174. $pdf = $gis_obj->prepareRowAsPdf(
  175. $sub_part,
  176. $label,
  177. $color,
  178. $scale_data,
  179. $pdf
  180. );
  181. }
  182. return $pdf;
  183. }
  184. /**
  185. * Prepares and returns the code related to a row in the GIS dataset as SVG.
  186. *
  187. * @param string $spatial GIS GEOMETRYCOLLECTION object
  188. * @param string $label label for the GIS GEOMETRYCOLLECTION object
  189. * @param string $color color for the GIS GEOMETRYCOLLECTION object
  190. * @param array $scale_data array containing data related to scaling
  191. *
  192. * @return string the code related to a row in the GIS dataset
  193. *
  194. * @access public
  195. */
  196. public function prepareRowAsSvg($spatial, $label, $color, array $scale_data)
  197. {
  198. $row = '';
  199. // Trim to remove leading 'GEOMETRYCOLLECTION(' and trailing ')'
  200. $goem_col
  201. = mb_substr(
  202. $spatial,
  203. 19,
  204. mb_strlen($spatial) - 20
  205. );
  206. // Split the geometry collection object to get its constituents.
  207. $sub_parts = $this->explodeGeomCol($goem_col);
  208. foreach ($sub_parts as $sub_part) {
  209. $type_pos = mb_strpos($sub_part, '(');
  210. if ($type_pos === false) {
  211. continue;
  212. }
  213. $type = mb_substr($sub_part, 0, $type_pos);
  214. $gis_obj = GisFactory::factory($type);
  215. if (! $gis_obj) {
  216. continue;
  217. }
  218. $row .= $gis_obj->prepareRowAsSvg(
  219. $sub_part,
  220. $label,
  221. $color,
  222. $scale_data
  223. );
  224. }
  225. return $row;
  226. }
  227. /**
  228. * Prepares JavaScript related to a row in the GIS dataset
  229. * to visualize it with OpenLayers.
  230. *
  231. * @param string $spatial GIS GEOMETRYCOLLECTION object
  232. * @param int $srid spatial reference ID
  233. * @param string $label label for the GIS GEOMETRYCOLLECTION object
  234. * @param array $color color for the GIS GEOMETRYCOLLECTION object
  235. * @param array $scale_data array containing data related to scaling
  236. *
  237. * @return string JavaScript related to a row in the GIS dataset
  238. *
  239. * @access public
  240. */
  241. public function prepareRowAsOl($spatial, $srid, $label, $color, array $scale_data)
  242. {
  243. $row = '';
  244. // Trim to remove leading 'GEOMETRYCOLLECTION(' and trailing ')'
  245. $goem_col
  246. = mb_substr(
  247. $spatial,
  248. 19,
  249. mb_strlen($spatial) - 20
  250. );
  251. // Split the geometry collection object to get its constituents.
  252. $sub_parts = $this->explodeGeomCol($goem_col);
  253. foreach ($sub_parts as $sub_part) {
  254. $type_pos = mb_strpos($sub_part, '(');
  255. if ($type_pos === false) {
  256. continue;
  257. }
  258. $type = mb_substr($sub_part, 0, $type_pos);
  259. $gis_obj = GisFactory::factory($type);
  260. if (! $gis_obj) {
  261. continue;
  262. }
  263. $row .= $gis_obj->prepareRowAsOl(
  264. $sub_part,
  265. $srid,
  266. $label,
  267. $color,
  268. $scale_data
  269. );
  270. }
  271. return $row;
  272. }
  273. /**
  274. * Splits the GEOMETRYCOLLECTION object and get its constituents.
  275. *
  276. * @param string $geom_col geometry collection string
  277. *
  278. * @return array the constituents of the geometry collection object
  279. *
  280. * @access private
  281. */
  282. private function explodeGeomCol($geom_col)
  283. {
  284. $sub_parts = [];
  285. $br_count = 0;
  286. $start = 0;
  287. $count = 0;
  288. foreach (str_split($geom_col) as $char) {
  289. if ($char === '(') {
  290. $br_count++;
  291. } elseif ($char === ')') {
  292. $br_count--;
  293. if ($br_count == 0) {
  294. $sub_parts[]
  295. = mb_substr(
  296. $geom_col,
  297. $start,
  298. $count + 1 - $start
  299. );
  300. $start = $count + 2;
  301. }
  302. }
  303. $count++;
  304. }
  305. return $sub_parts;
  306. }
  307. /**
  308. * Generates the WKT with the set of parameters passed by the GIS editor.
  309. *
  310. * @param array $gis_data GIS data
  311. * @param int $index index into the parameter object
  312. * @param string $empty value for empty points
  313. *
  314. * @return string WKT with the set of parameters passed by the GIS editor
  315. *
  316. * @access public
  317. */
  318. public function generateWkt(array $gis_data, $index, $empty = '')
  319. {
  320. $geom_count = $gis_data['GEOMETRYCOLLECTION']['geom_count'] ?? 1;
  321. $wkt = 'GEOMETRYCOLLECTION(';
  322. for ($i = 0; $i < $geom_count; $i++) {
  323. if (! isset($gis_data[$i]['gis_type'])) {
  324. continue;
  325. }
  326. $type = $gis_data[$i]['gis_type'];
  327. $gis_obj = GisFactory::factory($type);
  328. if (! $gis_obj) {
  329. continue;
  330. }
  331. $wkt .= $gis_obj->generateWkt($gis_data, $i, $empty) . ',';
  332. }
  333. if (isset($gis_data[0]['gis_type'])) {
  334. $wkt
  335. = mb_substr(
  336. $wkt,
  337. 0,
  338. mb_strlen($wkt) - 1
  339. );
  340. }
  341. return $wkt . ')';
  342. }
  343. /**
  344. * Generates parameters for the GIS data editor from the value of the GIS column.
  345. *
  346. * @param string $value of the GIS column
  347. *
  348. * @return array parameters for the GIS editor from the value of the GIS column
  349. *
  350. * @access public
  351. */
  352. public function generateParams($value)
  353. {
  354. $params = [];
  355. $data = GisGeometry::generateParams($value);
  356. $params['srid'] = $data['srid'];
  357. $wkt = $data['wkt'];
  358. // Trim to remove leading 'GEOMETRYCOLLECTION(' and trailing ')'
  359. $goem_col
  360. = mb_substr(
  361. $wkt,
  362. 19,
  363. mb_strlen($wkt) - 20
  364. );
  365. // Split the geometry collection object to get its constituents.
  366. $sub_parts = $this->explodeGeomCol($goem_col);
  367. $params['GEOMETRYCOLLECTION']['geom_count'] = count($sub_parts);
  368. $i = 0;
  369. foreach ($sub_parts as $sub_part) {
  370. $type_pos = mb_strpos($sub_part, '(');
  371. if ($type_pos === false) {
  372. continue;
  373. }
  374. $type = mb_substr($sub_part, 0, $type_pos);
  375. /**
  376. * @var GisMultiPolygon|GisPolygon|GisMultiPoint|GisPoint|GisMultiLineString|GisLineString $gis_obj
  377. */
  378. $gis_obj = GisFactory::factory($type);
  379. if (! $gis_obj) {
  380. continue;
  381. }
  382. $params = array_merge($params, $gis_obj->generateParams($sub_part, $i));
  383. $i++;
  384. }
  385. return $params;
  386. }
  387. }