@@ -2000,4 +2000,243 @@ public function getSupportForSpatialAxisOrder(): bool
20002000 {
20012001 return false ;
20022002 }
2003+
2004+ public function decodePoint (string $ wkb ): array
2005+ {
2006+ if (str_starts_with (strtoupper ($ wkb ), 'POINT( ' )) {
2007+ $ start = strpos ($ wkb , '( ' ) + 1 ;
2008+ $ end = strrpos ($ wkb , ') ' );
2009+ $ inside = substr ($ wkb , $ start , $ end - $ start );
2010+
2011+ $ coords = explode (' ' , trim ($ inside ));
2012+ return [(float )$ coords [0 ], (float )$ coords [1 ]];
2013+ }
2014+
2015+ $ bin = hex2bin ($ wkb );
2016+ if ($ bin === false ) {
2017+ throw new DatabaseException ('Invalid hex WKB string ' );
2018+ }
2019+
2020+ if (strlen ($ bin ) < 13 ) { // 1 byte endian + 4 bytes type + 8 bytes for X
2021+ throw new DatabaseException ('WKB too short ' );
2022+ }
2023+
2024+ $ isLE = ord ($ bin [0 ]) === 1 ;
2025+
2026+ // Type (4 bytes)
2027+ $ typeBytes = substr ($ bin , 1 , 4 );
2028+ if (strlen ($ typeBytes ) !== 4 ) {
2029+ throw new DatabaseException ('Failed to extract type bytes from WKB ' );
2030+ }
2031+
2032+ $ typeArr = unpack ($ isLE ? 'V ' : 'N ' , $ typeBytes );
2033+ if ($ typeArr === false || !isset ($ typeArr [1 ])) {
2034+ throw new DatabaseException ('Failed to unpack type from WKB ' );
2035+ }
2036+ $ type = $ typeArr [1 ];
2037+
2038+ // Offset to coordinates (skip SRID if present)
2039+ $ offset = 5 + (($ type & 0x20000000 ) ? 4 : 0 );
2040+
2041+ if (strlen ($ bin ) < $ offset + 16 ) { // 16 bytes for X,Y
2042+ throw new DatabaseException ('WKB too short for coordinates ' );
2043+ }
2044+
2045+ $ fmt = $ isLE ? 'e ' : 'E ' ; // little vs big endian double
2046+
2047+ // X coordinate
2048+ $ xArr = unpack ($ fmt , substr ($ bin , $ offset , 8 ));
2049+ if ($ xArr === false || !isset ($ xArr [1 ])) {
2050+ throw new DatabaseException ('Failed to unpack X coordinate ' );
2051+ }
2052+ $ x = (float )$ xArr [1 ];
2053+
2054+ // Y coordinate
2055+ $ yArr = unpack ($ fmt , substr ($ bin , $ offset + 8 , 8 ));
2056+ if ($ yArr === false || !isset ($ yArr [1 ])) {
2057+ throw new DatabaseException ('Failed to unpack Y coordinate ' );
2058+ }
2059+ $ y = (float )$ yArr [1 ];
2060+
2061+ return [$ x , $ y ];
2062+ }
2063+
2064+ public function decodeLinestring (mixed $ wkb ): array
2065+ {
2066+ if (str_starts_with (strtoupper ($ wkb ), 'LINESTRING( ' )) {
2067+ $ start = strpos ($ wkb , '( ' ) + 1 ;
2068+ $ end = strrpos ($ wkb , ') ' );
2069+ $ inside = substr ($ wkb , $ start , $ end - $ start );
2070+
2071+ $ points = explode (', ' , $ inside );
2072+ return array_map (function ($ point ) {
2073+ $ coords = explode (' ' , trim ($ point ));
2074+ return [(float )$ coords [0 ], (float )$ coords [1 ]];
2075+ }, $ points );
2076+ }
2077+
2078+ if (ctype_xdigit ($ wkb )) {
2079+ $ wkb = hex2bin ($ wkb );
2080+ if ($ wkb === false ) {
2081+ throw new DatabaseException ("Failed to convert hex WKB to binary. " );
2082+ }
2083+ }
2084+
2085+ if (strlen ($ wkb ) < 9 ) {
2086+ throw new DatabaseException ("WKB too short to be a valid geometry " );
2087+ }
2088+
2089+ $ byteOrder = ord ($ wkb [0 ]);
2090+ if ($ byteOrder === 0 ) {
2091+ throw new DatabaseException ("Big-endian WKB not supported " );
2092+ } elseif ($ byteOrder !== 1 ) {
2093+ throw new DatabaseException ("Invalid byte order in WKB " );
2094+ }
2095+
2096+ // Type + SRID flag
2097+ $ typeField = unpack ('V ' , substr ($ wkb , 1 , 4 ));
2098+ if ($ typeField === false ) {
2099+ throw new DatabaseException ('Failed to unpack the type field from WKB. ' );
2100+ }
2101+
2102+ $ typeField = $ typeField [1 ];
2103+ $ geomType = $ typeField & 0xFF ;
2104+ $ hasSRID = ($ typeField & 0x20000000 ) !== 0 ;
2105+
2106+ if ($ geomType !== 2 ) { // 2 = LINESTRING
2107+ throw new DatabaseException ("Not a LINESTRING geometry type, got {$ geomType }" );
2108+ }
2109+
2110+ $ offset = 5 ;
2111+ if ($ hasSRID ) {
2112+ $ offset += 4 ;
2113+ }
2114+
2115+ $ numPoints = unpack ('V ' , substr ($ wkb , $ offset , 4 ));
2116+ if ($ numPoints === false ) {
2117+ throw new DatabaseException ("Failed to unpack number of points at offset {$ offset }. " );
2118+ }
2119+
2120+ $ numPoints = $ numPoints [1 ];
2121+ $ offset += 4 ;
2122+
2123+ $ points = [];
2124+ for ($ i = 0 ; $ i < $ numPoints ; $ i ++) {
2125+ $ x = unpack ('e ' , substr ($ wkb , $ offset , 8 ));
2126+ if ($ x === false ) {
2127+ throw new DatabaseException ("Failed to unpack X coordinate at offset {$ offset }. " );
2128+ }
2129+
2130+ $ x = (float ) $ x [1 ];
2131+
2132+ $ offset += 8 ;
2133+
2134+ $ y = unpack ('e ' , substr ($ wkb , $ offset , 8 ));
2135+ if ($ y === false ) {
2136+ throw new DatabaseException ("Failed to unpack Y coordinate at offset {$ offset }. " );
2137+ }
2138+
2139+ $ y = (float ) $ y [1 ];
2140+
2141+ $ offset += 8 ;
2142+ $ points [] = [$ x , $ y ];
2143+ }
2144+
2145+ return $ points ;
2146+ }
2147+
2148+ public function decodePolygon (string $ wkb ): array
2149+ {
2150+ // POLYGON((x1,y1),(x2,y2))
2151+ if (str_starts_with ($ wkb , 'POLYGON(( ' )) {
2152+ $ start = strpos ($ wkb , '(( ' ) + 2 ;
2153+ $ end = strrpos ($ wkb , ')) ' );
2154+ $ inside = substr ($ wkb , $ start , $ end - $ start );
2155+
2156+ $ rings = explode ('),( ' , $ inside );
2157+ return array_map (function ($ ring ) {
2158+ $ points = explode (', ' , $ ring );
2159+ return array_map (function ($ point ) {
2160+ $ coords = explode (' ' , trim ($ point ));
2161+ return [(float )$ coords [0 ], (float )$ coords [1 ]];
2162+ }, $ points );
2163+ }, $ rings );
2164+ }
2165+
2166+ // Convert hex string to binary if needed
2167+ if (preg_match ('/^[0-9a-fA-F]+$/ ' , $ wkb )) {
2168+ $ wkb = hex2bin ($ wkb );
2169+ if ($ wkb === false ) {
2170+ throw new DatabaseException ("Invalid hex WKB " );
2171+ }
2172+ }
2173+
2174+ if (strlen ($ wkb ) < 9 ) {
2175+ throw new DatabaseException ("WKB too short " );
2176+ }
2177+
2178+ $ uInt32 = 'V ' ; // little-endian 32-bit unsigned
2179+ $ uDouble = 'd ' ; // little-endian double
2180+
2181+ $ typeInt = unpack ($ uInt32 , substr ($ wkb , 1 , 4 ));
2182+ if ($ typeInt === false ) {
2183+ throw new DatabaseException ('Failed to unpack type field from WKB. ' );
2184+ }
2185+
2186+ $ typeInt = (int ) $ typeInt [1 ];
2187+ $ hasSrid = ($ typeInt & 0x20000000 ) !== 0 ;
2188+ $ geomType = $ typeInt & 0xFF ;
2189+
2190+ if ($ geomType !== 3 ) { // 3 = POLYGON
2191+ throw new DatabaseException ("Not a POLYGON geometry type, got {$ geomType }" );
2192+ }
2193+
2194+ $ offset = 5 ;
2195+ if ($ hasSrid ) {
2196+ $ offset += 4 ;
2197+ }
2198+
2199+ // Number of rings
2200+ $ numRings = unpack ($ uInt32 , substr ($ wkb , $ offset , 4 ));
2201+ if ($ numRings === false ) {
2202+ throw new DatabaseException ('Failed to unpack number of rings from WKB. ' );
2203+ }
2204+
2205+ $ numRings = (int ) $ numRings [1 ];
2206+ $ offset += 4 ;
2207+
2208+ $ rings = [];
2209+ for ($ r = 0 ; $ r < $ numRings ; $ r ++) {
2210+ $ numPoints = unpack ($ uInt32 , substr ($ wkb , $ offset , 4 ));
2211+ if ($ numPoints === false ) {
2212+ throw new DatabaseException ('Failed to unpack number of points from WKB. ' );
2213+ }
2214+
2215+ $ numPoints = (int ) $ numPoints [1 ];
2216+ $ offset += 4 ;
2217+ $ points = [];
2218+ for ($ i = 0 ; $ i < $ numPoints ; $ i ++) {
2219+ $ x = unpack ($ uDouble , substr ($ wkb , $ offset , 8 ));
2220+ if ($ x === false ) {
2221+ throw new DatabaseException ('Failed to unpack X coordinate from WKB. ' );
2222+ }
2223+
2224+ $ x = (float ) $ x [1 ];
2225+
2226+ $ y = unpack ($ uDouble , substr ($ wkb , $ offset + 8 , 8 ));
2227+ if ($ y === false ) {
2228+ throw new DatabaseException ('Failed to unpack Y coordinate from WKB. ' );
2229+ }
2230+
2231+ $ y = (float ) $ y [1 ];
2232+
2233+ $ points [] = [$ x , $ y ];
2234+ $ offset += 16 ;
2235+ }
2236+ $ rings [] = $ points ;
2237+ }
2238+
2239+ return $ rings ; // array of rings, each ring is array of [x,y]
2240+ }
2241+
20032242}
0 commit comments