├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── doc └── api.html ├── geoPHP.inc ├── lib ├── adapters │ ├── EWKB.class.php │ ├── EWKT.class.php │ ├── GPX.class.php │ ├── GeoAdapter.class.php │ ├── GeoHash.class.php │ ├── GeoJSON.class.php │ ├── GeoRSS.class.php │ ├── GoogleGeocode.class.php │ ├── KML.class.php │ ├── WKB.class.php │ └── WKT.class.php └── geometry │ ├── Collection.class.php │ ├── Geometry.class.php │ ├── GeometryCollection.class.php │ ├── LineString.class.php │ ├── MultiLineString.class.php │ ├── MultiPoint.class.php │ ├── MultiPolygon.class.php │ ├── Point.class.php │ └── Polygon.class.php └── tests ├── input ├── 20120702.gpx ├── an_empty_polygon.wkt ├── barret_spur.gpx ├── big_n_ugly.kml ├── box.georss ├── cdata.kml ├── circle.georss ├── empty_point.wkt ├── fells_loop.gpx ├── geometrycollection.georss ├── geometrycollection.wkt ├── line.georss ├── linestring.wkt ├── long.geohash ├── multilinestring.ewkt ├── multilinestring.wkt ├── multipolygon.wkb ├── multipolygon.wkt ├── multipolygon2.wkt ├── multipolygon_big.wkt ├── opposite.gpx ├── path.kml ├── paths_big.json ├── pentagon.kml ├── point.georss ├── point.kml ├── point.wkt ├── polygon.georss ├── polygon.wkt ├── polygon2.wkt ├── polygon3.wkt ├── polygon4.wkt ├── polygon_spaces.wkt ├── route.gpx ├── short.geohash ├── simple_point.json └── track.gpx ├── postgis.php ├── test.php └── tests ├── 20120702Test.php ├── adaptersTest.php ├── bootstrap.php ├── geohashTest.php ├── geosTest.php ├── methodsTest.php └── placeholdersTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # docs available at http://docs.travis-ci.com/user/languages/php/ 2 | # example available at https://github.com/travis-ci/travis-ci-php-example 3 | language: php 4 | 5 | before_script: 6 | - composer self-update 7 | 8 | env: GEOPHP_RUN_TESTS=1 9 | 10 | install: 11 | - composer install 12 | # TODO Install geos library -- as a matrix test 13 | # TODO optionally set up a postgis database for testing 14 | 15 | script: 16 | - cd tests 17 | - phpunit --verbose --colors --stderr tests 18 | - php test.php 19 | 20 | # run tests on the following versions 21 | php: 22 | - 5.6 23 | - 5.5 24 | - 5.4 25 | - 5.3 26 | - hhvm 27 | 28 | matrix: 29 | fast_finish: false 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, Patrick Hayes and contributors 2 | 3 | This program is dual-licensed under both the GPL version 2 (or later) and 4 | Modified BSD License. Either license may be used at your option. 5 | 6 | ------------------------------------------------------------------------------ 7 | Modified BSD License 8 | 9 | Redistribution and use in source and binary forms, with or without 10 | modification, are permitted provided that the following conditions are met: 11 | * Redistributions of source code must retain the above copyright 12 | notice, this list of conditions and the following disclaimer. 13 | * Redistributions in binary form must reproduce the above copyright 14 | notice, this list of conditions and the following disclaimer in the 15 | documentation and/or other materials provided with the distribution. 16 | * Neither the name of the copyright holder nor the 17 | names of the contributors may be used to endorse or promote products 18 | derived from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY 24 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 27 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | ------------------------------------------------------------------------------- 32 | GNU GENERAL PUBLIC LICENSE 33 | Version 2, June 1991 34 | 35 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 36 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 37 | Everyone is permitted to copy and distribute verbatim copies 38 | of this license document, but changing it is not allowed. 39 | 40 | Preamble 41 | 42 | The licenses for most software are designed to take away your 43 | freedom to share and change it. By contrast, the GNU General Public 44 | License is intended to guarantee your freedom to share and change free 45 | software--to make sure the software is free for all its users. This 46 | General Public License applies to most of the Free Software 47 | Foundation's software and to any other program whose authors commit to 48 | using it. (Some other Free Software Foundation software is covered by 49 | the GNU Lesser General Public License instead.) You can apply it to 50 | your programs, too. 51 | 52 | When we speak of free software, we are referring to freedom, not 53 | price. Our General Public Licenses are designed to make sure that you 54 | have the freedom to distribute copies of free software (and charge for 55 | this service if you wish), that you receive source code or can get it 56 | if you want it, that you can change the software or use pieces of it 57 | in new free programs; and that you know you can do these things. 58 | 59 | To protect your rights, we need to make restrictions that forbid 60 | anyone to deny you these rights or to ask you to surrender the rights. 61 | These restrictions translate to certain responsibilities for you if you 62 | distribute copies of the software, or if you modify it. 63 | 64 | For example, if you distribute copies of such a program, whether 65 | gratis or for a fee, you must give the recipients all the rights that 66 | you have. You must make sure that they, too, receive or can get the 67 | source code. And you must show them these terms so they know their 68 | rights. 69 | 70 | We protect your rights with two steps: (1) copyright the software, and 71 | (2) offer you this license which gives you legal permission to copy, 72 | distribute and/or modify the software. 73 | 74 | Also, for each author's protection and ours, we want to make certain 75 | that everyone understands that there is no warranty for this free 76 | software. If the software is modified by someone else and passed on, we 77 | want its recipients to know that what they have is not the original, so 78 | that any problems introduced by others will not reflect on the original 79 | authors' reputations. 80 | 81 | Finally, any free program is threatened constantly by software 82 | patents. We wish to avoid the danger that redistributors of a free 83 | program will individually obtain patent licenses, in effect making the 84 | program proprietary. To prevent this, we have made it clear that any 85 | patent must be licensed for everyone's free use or not licensed at all. 86 | 87 | The precise terms and conditions for copying, distribution and 88 | modification follow. 89 | 90 | GNU GENERAL PUBLIC LICENSE 91 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 92 | 93 | 0. This License applies to any program or other work which contains 94 | a notice placed by the copyright holder saying it may be distributed 95 | under the terms of this General Public License. The "Program", below, 96 | refers to any such program or work, and a "work based on the Program" 97 | means either the Program or any derivative work under copyright law: 98 | that is to say, a work containing the Program or a portion of it, 99 | either verbatim or with modifications and/or translated into another 100 | language. (Hereinafter, translation is included without limitation in 101 | the term "modification".) Each licensee is addressed as "you". 102 | 103 | Activities other than copying, distribution and modification are not 104 | covered by this License; they are outside its scope. The act of 105 | running the Program is not restricted, and the output from the Program 106 | is covered only if its contents constitute a work based on the 107 | Program (independent of having been made by running the Program). 108 | Whether that is true depends on what the Program does. 109 | 110 | 1. You may copy and distribute verbatim copies of the Program's 111 | source code as you receive it, in any medium, provided that you 112 | conspicuously and appropriately publish on each copy an appropriate 113 | copyright notice and disclaimer of warranty; keep intact all the 114 | notices that refer to this License and to the absence of any warranty; 115 | and give any other recipients of the Program a copy of this License 116 | along with the Program. 117 | 118 | You may charge a fee for the physical act of transferring a copy, and 119 | you may at your option offer warranty protection in exchange for a fee. 120 | 121 | 2. You may modify your copy or copies of the Program or any portion 122 | of it, thus forming a work based on the Program, and copy and 123 | distribute such modifications or work under the terms of Section 1 124 | above, provided that you also meet all of these conditions: 125 | 126 | a) You must cause the modified files to carry prominent notices 127 | stating that you changed the files and the date of any change. 128 | 129 | b) You must cause any work that you distribute or publish, that in 130 | whole or in part contains or is derived from the Program or any 131 | part thereof, to be licensed as a whole at no charge to all third 132 | parties under the terms of this License. 133 | 134 | c) If the modified program normally reads commands interactively 135 | when run, you must cause it, when started running for such 136 | interactive use in the most ordinary way, to print or display an 137 | announcement including an appropriate copyright notice and a 138 | notice that there is no warranty (or else, saying that you provide 139 | a warranty) and that users may redistribute the program under 140 | these conditions, and telling the user how to view a copy of this 141 | License. (Exception: if the Program itself is interactive but 142 | does not normally print such an announcement, your work based on 143 | the Program is not required to print an announcement.) 144 | 145 | These requirements apply to the modified work as a whole. If 146 | identifiable sections of that work are not derived from the Program, 147 | and can be reasonably considered independent and separate works in 148 | themselves, then this License, and its terms, do not apply to those 149 | sections when you distribute them as separate works. But when you 150 | distribute the same sections as part of a whole which is a work based 151 | on the Program, the distribution of the whole must be on the terms of 152 | this License, whose permissions for other licensees extend to the 153 | entire whole, and thus to each and every part regardless of who wrote it. 154 | 155 | Thus, it is not the intent of this section to claim rights or contest 156 | your rights to work written entirely by you; rather, the intent is to 157 | exercise the right to control the distribution of derivative or 158 | collective works based on the Program. 159 | 160 | In addition, mere aggregation of another work not based on the Program 161 | with the Program (or with a work based on the Program) on a volume of 162 | a storage or distribution medium does not bring the other work under 163 | the scope of this License. 164 | 165 | 3. You may copy and distribute the Program (or a work based on it, 166 | under Section 2) in object code or executable form under the terms of 167 | Sections 1 and 2 above provided that you also do one of the following: 168 | 169 | a) Accompany it with the complete corresponding machine-readable 170 | source code, which must be distributed under the terms of Sections 171 | 1 and 2 above on a medium customarily used for software interchange; or, 172 | 173 | b) Accompany it with a written offer, valid for at least three 174 | years, to give any third party, for a charge no more than your 175 | cost of physically performing source distribution, a complete 176 | machine-readable copy of the corresponding source code, to be 177 | distributed under the terms of Sections 1 and 2 above on a medium 178 | customarily used for software interchange; or, 179 | 180 | c) Accompany it with the information you received as to the offer 181 | to distribute corresponding source code. (This alternative is 182 | allowed only for noncommercial distribution and only if you 183 | received the program in object code or executable form with such 184 | an offer, in accord with Subsection b above.) 185 | 186 | The source code for a work means the preferred form of the work for 187 | making modifications to it. For an executable work, complete source 188 | code means all the source code for all modules it contains, plus any 189 | associated interface definition files, plus the scripts used to 190 | control compilation and installation of the executable. However, as a 191 | special exception, the source code distributed need not include 192 | anything that is normally distributed (in either source or binary 193 | form) with the major components (compiler, kernel, and so on) of the 194 | operating system on which the executable runs, unless that component 195 | itself accompanies the executable. 196 | 197 | If distribution of executable or object code is made by offering 198 | access to copy from a designated place, then offering equivalent 199 | access to copy the source code from the same place counts as 200 | distribution of the source code, even though third parties are not 201 | compelled to copy the source along with the object code. 202 | 203 | 4. You may not copy, modify, sublicense, or distribute the Program 204 | except as expressly provided under this License. Any attempt 205 | otherwise to copy, modify, sublicense or distribute the Program is 206 | void, and will automatically terminate your rights under this License. 207 | However, parties who have received copies, or rights, from you under 208 | this License will not have their licenses terminated so long as such 209 | parties remain in full compliance. 210 | 211 | 5. You are not required to accept this License, since you have not 212 | signed it. However, nothing else grants you permission to modify or 213 | distribute the Program or its derivative works. These actions are 214 | prohibited by law if you do not accept this License. Therefore, by 215 | modifying or distributing the Program (or any work based on the 216 | Program), you indicate your acceptance of this License to do so, and 217 | all its terms and conditions for copying, distributing or modifying 218 | the Program or works based on it. 219 | 220 | 6. Each time you redistribute the Program (or any work based on the 221 | Program), the recipient automatically receives a license from the 222 | original licensor to copy, distribute or modify the Program subject to 223 | these terms and conditions. You may not impose any further 224 | restrictions on the recipients' exercise of the rights granted herein. 225 | You are not responsible for enforcing compliance by third parties to 226 | this License. 227 | 228 | 7. If, as a consequence of a court judgment or allegation of patent 229 | infringement or for any other reason (not limited to patent issues), 230 | conditions are imposed on you (whether by court order, agreement or 231 | otherwise) that contradict the conditions of this License, they do not 232 | excuse you from the conditions of this License. If you cannot 233 | distribute so as to satisfy simultaneously your obligations under this 234 | License and any other pertinent obligations, then as a consequence you 235 | may not distribute the Program at all. For example, if a patent 236 | license would not permit royalty-free redistribution of the Program by 237 | all those who receive copies directly or indirectly through you, then 238 | the only way you could satisfy both it and this License would be to 239 | refrain entirely from distribution of the Program. 240 | 241 | If any portion of this section is held invalid or unenforceable under 242 | any particular circumstance, the balance of the section is intended to 243 | apply and the section as a whole is intended to apply in other 244 | circumstances. 245 | 246 | It is not the purpose of this section to induce you to infringe any 247 | patents or other property right claims or to contest validity of any 248 | such claims; this section has the sole purpose of protecting the 249 | integrity of the free software distribution system, which is 250 | implemented by public license practices. Many people have made 251 | generous contributions to the wide range of software distributed 252 | through that system in reliance on consistent application of that 253 | system; it is up to the author/donor to decide if he or she is willing 254 | to distribute software through any other system and a licensee cannot 255 | impose that choice. 256 | 257 | This section is intended to make thoroughly clear what is believed to 258 | be a consequence of the rest of this License. 259 | 260 | 8. If the distribution and/or use of the Program is restricted in 261 | certain countries either by patents or by copyrighted interfaces, the 262 | original copyright holder who places the Program under this License 263 | may add an explicit geographical distribution limitation excluding 264 | those countries, so that distribution is permitted only in or among 265 | countries not thus excluded. In such case, this License incorporates 266 | the limitation as if written in the body of this License. 267 | 268 | 9. The Free Software Foundation may publish revised and/or new versions 269 | of the General Public License from time to time. Such new versions will 270 | be similar in spirit to the present version, but may differ in detail to 271 | address new problems or concerns. 272 | 273 | Each version is given a distinguishing version number. If the Program 274 | specifies a version number of this License which applies to it and "any 275 | later version", you have the option of following the terms and conditions 276 | either of that version or of any later version published by the Free 277 | Software Foundation. If the Program does not specify a version number of 278 | this License, you may choose any version ever published by the Free Software 279 | Foundation. 280 | 281 | 10. If you wish to incorporate parts of the Program into other free 282 | programs whose distribution conditions are different, write to the author 283 | to ask for permission. For software which is copyrighted by the Free 284 | Software Foundation, write to the Free Software Foundation; we sometimes 285 | make exceptions for this. Our decision will be guided by the two goals 286 | of preserving the free status of all derivatives of our free software and 287 | of promoting the sharing and reuse of software generally. 288 | 289 | NO WARRANTY 290 | 291 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 292 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 293 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 294 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 295 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 296 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 297 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 298 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 299 | REPAIR OR CORRECTION. 300 | 301 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 302 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 303 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 304 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 305 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 306 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 307 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 308 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 309 | POSSIBILITY OF SUCH DAMAGES. 310 | 311 | END OF TERMS AND CONDITIONS 312 | 313 | How to Apply These Terms to Your New Programs 314 | 315 | If you develop a new program, and you want it to be of the greatest 316 | possible use to the public, the best way to achieve this is to make it 317 | free software which everyone can redistribute and change under these terms. 318 | 319 | To do so, attach the following notices to the program. It is safest 320 | to attach them to the start of each source file to most effectively 321 | convey the exclusion of warranty; and each file should have at least 322 | the "copyright" line and a pointer to where the full notice is found. 323 | 324 | 325 | Copyright (C) 326 | 327 | This program is free software; you can redistribute it and/or modify 328 | it under the terms of the GNU General Public License as published by 329 | the Free Software Foundation; either version 2 of the License, or 330 | (at your option) any later version. 331 | 332 | This program is distributed in the hope that it will be useful, 333 | but WITHOUT ANY WARRANTY; without even the implied warranty of 334 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 335 | GNU General Public License for more details. 336 | 337 | You should have received a copy of the GNU General Public License along 338 | with this program; if not, write to the Free Software Foundation, Inc., 339 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 340 | 341 | Also add information on how to contact you by electronic and paper mail. 342 | 343 | If the program is interactive, make it output a short notice like this 344 | when it starts in an interactive mode: 345 | 346 | Gnomovision version 69, Copyright (C) year name of author 347 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 348 | This is free software, and you are welcome to redistribute it 349 | under certain conditions; type `show c' for details. 350 | 351 | The hypothetical commands `show w' and `show c' should show the appropriate 352 | parts of the General Public License. Of course, the commands you use may 353 | be called something other than `show w' and `show c'; they could even be 354 | mouse-clicks or menu items--whatever suits your program. 355 | 356 | You should also get your employer (if you work as a programmer) or your 357 | school, if any, to sign a "copyright disclaimer" for the program, if 358 | necessary. Here is a sample; alter the names: 359 | 360 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 361 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 362 | 363 | , 1 April 1989 364 | Ty Coon, President of Vice 365 | 366 | This General Public License does not permit incorporating your program into 367 | proprietary programs. If your program is a subroutine library, you may 368 | consider it more useful to permit linking proprietary applications with the 369 | library. If this is what you want to do, use the GNU Lesser General 370 | Public License instead of this License. 371 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/phayes/geoPHP.svg?branch=master)](https://travis-ci.org/phayes/geoPHP) 2 | 3 | [geophp.net](https://geophp.net "GeoPHP homepage") 4 | 5 | 6 | GeoPHP is a open-source native PHP library for doing geometry operations. It is written entirely in PHP and 7 | can therefore run on shared hosts. It can read and write a wide variety of formats: WKT (including EWKT), WKB (including EWKB), GeoJSON, 8 | KML, GPX, and GeoRSS. It works with all Simple-Feature geometries (Point, LineString, Polygon, GeometryCollection etc.) 9 | and can be used to get centroids, bounding-boxes, area, and a wide variety of other useful information. 10 | 11 | geoPHP also helpfully wraps the GEOS php extension so that applications can get a transparent performance 12 | increase when GEOS is installed on the server. When GEOS is installed, geoPHP also becomes 13 | fully compliant with the OpenGIS® Implementation Standard for Geographic information. With GEOS you get the 14 | full-set of openGIS functions in PHP like Union, IsWithin, Touches etc. This means that applications 15 | get a useful "core-set" of geometry operations that work in all environments, and an "extended-set"of operations 16 | for environments that have GEOS installed. 17 | 18 | See the 'getting started' section below for references and examples of everything that geoPHP can do. 19 | 20 | This project is currently looking for co-maintainers. If you think you can help out, please send me a 21 | message. Forks are also welcome, please issue pull requests and I will merge them into the main branch. 22 | 23 | Getting Started 24 | ----------------------- 25 | 26 | * The lastest stable version can always be downloaded at: 27 | * Read the API Reference at: 28 | * Examples 29 | * Using geoPHP as a GIS format converter: 30 | * Other Interesting Links: 31 | * Learn about GEOS integration at: 32 | 33 | Example usage 34 | ------------------------------------------------- 35 | 36 | ```php 37 | getArea(); 43 | $centroid = $polygon->getCentroid(); 44 | $centX = $centroid->getX(); 45 | $centY = $centroid->getY(); 46 | 47 | print "This polygon has an area of ".$area." and a centroid with X=".$centX." and Y=".$centY; 48 | 49 | // MultiPoint json example 50 | print "
"; 51 | $json = 52 | '{ 53 | "type": "MultiPoint", 54 | "coordinates": [ 55 | [100.0, 0.0], [101.0, 1.0] 56 | ] 57 | }'; 58 | 59 | $multipoint = geoPHP::load($json, 'json'); 60 | $multipoint_points = $multipoint->getComponents(); 61 | $first_wkt = $multipoint_points[0]->out('wkt'); 62 | 63 | print "This multipoint has ".$multipoint->numGeometries()." points. The first point has a wkt representation of ".$first_wkt; 64 | ``` 65 | ======= 66 | 67 | More Examples 68 | ------------------------------------------------- 69 | 70 | The Well Known Text (WKT) and Well Known Binary (WKB) support is ideal for integrating with MySQL's or PostGIS's spatial capability. 71 | Once you have SELECTed your data with `'AsText('geo_field')'` or `'AsBinary('geo_field')'`, you can put it straight into 72 | geoPHP (can be wkt or wkb, but must be the same as how you extracted it from your database): 73 | 74 | $geom = geoPHP::load($dbRow,'wkt'); 75 | 76 | You can collect multiple geometries into one (note that you must use wkt for this): 77 | 78 | $geom = geoPHP::load("GEOMETRYCOLLECTION(".$dbString1.",".$dbString2.")",'wkt'); 79 | 80 | Calling get components returns the sub-geometries within a geometry as an array. 81 | 82 | $geom2 = geoPHP::load("GEOMETRYCOLLECTION(LINESTRING(1 1,5 1,5 5,1 5,1 1),LINESTRING(2 2,2 3,3 3,3 2,2 2))"); 83 | $geomComponents = $geom2->getComponents(); //an array of the two linestring geometries 84 | $linestring1 = $geomComponents[0]->getComponents(); //an array of the first linestring's point geometries 85 | $linestring2 = $geomComponents[1]->getComponents(); 86 | echo $linestring1[0]->x() . ", " . $linestring1[0]->y(); //outputs '1, 1' 87 | 88 | An alternative is to use the `asArray()` method. Using the above geometry collection of two linestrings, 89 | 90 | $geometryArray = $geom2->asArray(); 91 | echo $geometryArray[0][0][0] . ", " . $geometryArray[0][0][1]; //outputs '1, 1' 92 | 93 | Clearly, more complex analysis is possible. 94 | 95 | echo $geom2->envelope()->area(); 96 | 97 | 98 | Working with PostGIS 99 | --------------------- 100 | geoPHP, through it's EWKB adapter, has good integration with postGIS. Here's an example of reading and writing postGIS geometries 101 | 102 | ```php 103 | out('ewkb')); 125 | pg_query($connection, "INSERT INTO $table ($column) values (GeomFromWKB('$insert_string'))"); 126 | } 127 | 128 | // Using a direct SELECT and INSERTs in PostGIS without using wrapping functions 129 | $result = pg_fetch_all(pg_query($connection, "SELECT $column as geom FROM $table")); 130 | foreach ($result as $item) { 131 | $wkb = pack('H*',$item['geom']); // Unpacking the hex blob 132 | $geom = geoPHP::load($wkb, 'ewkb'); // We now have a geoPHP Geometry 133 | 134 | // To insert directly into postGIS we need to unpack the WKB 135 | $unpacked = unpack('H*', $geom->out('ewkb')); 136 | $insert_string = $unpacked[1]; 137 | pg_query($connection, "INSERT INTO $table ($column) values ('$insert_string')"); 138 | } 139 | ``` 140 | 141 | 142 | Credit 143 | ------------------------------------------------- 144 | 145 | Maintainer: Patrick Hayes 146 | 147 | Additional Contributors: 148 | 149 | * GeoMemes Research () 150 | * HighWire Press () and GeoScienceWorld () 151 | * Arnaud Renevier (gisconverter.php) 152 | * Dave Tarc 153 | * Elliott Hunston (documentation) 154 | 155 | This library is open-source and dual-licensed under both the Modified BSD License and GPLv2. Either license may be used at your option. 156 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phayes/geophp", 3 | "license": "GPL-2 or New-BSD", 4 | "type": "library", 5 | "description": "GeoPHP is a open-source native PHP library for doing geometry operations. It is written entirely in PHP and can therefore run on shared hosts. It can read and write a wide variety of formats: WKT (including EWKT), WKB (including EWKB), GeoJSON, KML, GPX, GeoRSS). It works with all Simple-Feature geometries (Point, LineString, Polygon, GeometryCollection etc.) and can be used to get centroids, bounding-boxes, area, and a wide variety of other useful information.", 6 | "homepage": "https://github.com/phayes/geoPHP", 7 | "autoload": { 8 | "classmap": ["geoPHP.inc"] 9 | }, 10 | "authors":[ 11 | { 12 | "name":"Patrick Hayes" 13 | } 14 | ], 15 | "require-dev": { 16 | "phpunit/phpunit": "4.1.*" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /geoPHP.inc: -------------------------------------------------------------------------------- 1 | 'WKT', 95 | 'ewkt' => 'EWKT', 96 | 'wkb' => 'WKB', 97 | 'ewkb' => 'EWKB', 98 | 'json' => 'GeoJSON', 99 | 'geojson' => 'GeoJSON', 100 | 'kml' => 'KML', 101 | 'gpx' => 'GPX', 102 | 'georss' => 'GeoRSS', 103 | 'google_geocode' => 'GoogleGeocode', 104 | 'geohash' => 'GeoHash', 105 | ); 106 | } 107 | 108 | static function geometryList() { 109 | return array( 110 | 'point' => 'Point', 111 | 'linestring' => 'LineString', 112 | 'polygon' => 'Polygon', 113 | 'multipoint' => 'MultiPoint', 114 | 'multilinestring' => 'MultiLineString', 115 | 'multipolygon' => 'MultiPolygon', 116 | 'geometrycollection' => 'GeometryCollection', 117 | ); 118 | } 119 | 120 | static function geosInstalled($force = NULL) { 121 | static $geos_installed = NULL; 122 | if ($force !== NULL) $geos_installed = $force; 123 | if ($geos_installed !== NULL) { 124 | return $geos_installed; 125 | } 126 | $geos_installed = class_exists('GEOSGeometry'); 127 | return $geos_installed; 128 | } 129 | 130 | static function geosToGeometry($geos) { 131 | if (!geoPHP::geosInstalled()) { 132 | return NULL; 133 | } 134 | $wkb_writer = new GEOSWKBWriter(); 135 | $wkb = $wkb_writer->writeHEX($geos); 136 | $geometry = geoPHP::load($wkb, 'wkb', TRUE); 137 | if ($geometry) { 138 | $geometry->setGeos($geos); 139 | return $geometry; 140 | } 141 | } 142 | 143 | // Reduce a geometry, or an array of geometries, into their 'lowest' available common geometry. 144 | // For example a GeometryCollection of only points will become a MultiPoint 145 | // A multi-point containing a single point will return a point. 146 | // An array of geometries can be passed and they will be compiled into a single geometry 147 | static function geometryReduce($geometry) { 148 | // If it's an array of one, then just parse the one 149 | if (is_array($geometry)) { 150 | if (empty($geometry)) return FALSE; 151 | if (count($geometry) == 1) return geoPHP::geometryReduce(array_shift($geometry)); 152 | } 153 | 154 | // If the geometry cannot even theoretically be reduced more, then pass it back 155 | if (gettype($geometry) == 'object') { 156 | $passbacks = array('Point','LineString','Polygon'); 157 | if (in_array($geometry->geometryType(),$passbacks)) { 158 | return $geometry; 159 | } 160 | } 161 | 162 | // If it is a mutlti-geometry, check to see if it just has one member 163 | // If it does, then pass the member, if not, then just pass back the geometry 164 | if (gettype($geometry) == 'object') { 165 | $simple_collections = array('MultiPoint','MultiLineString','MultiPolygon'); 166 | if (in_array(get_class($geometry),$passbacks)) { 167 | $components = $geometry->getComponents(); 168 | if (count($components) == 1) { 169 | return $components[0]; 170 | } 171 | else { 172 | return $geometry; 173 | } 174 | } 175 | } 176 | 177 | // So now we either have an array of geometries, a GeometryCollection, or an array of GeometryCollections 178 | if (!is_array($geometry)) { 179 | $geometry = array($geometry); 180 | } 181 | 182 | $geometries = array(); 183 | $geom_types = array(); 184 | 185 | $collections = array('MultiPoint','MultiLineString','MultiPolygon','GeometryCollection'); 186 | 187 | foreach ($geometry as $item) { 188 | if ($item) { 189 | if (in_array(get_class($item), $collections)) { 190 | foreach ($item->getComponents() as $component) { 191 | $geometries[] = $component; 192 | $geom_types[] = $component->geometryType(); 193 | } 194 | } 195 | else { 196 | $geometries[] = $item; 197 | $geom_types[] = $item->geometryType(); 198 | } 199 | } 200 | } 201 | 202 | $geom_types = array_unique($geom_types); 203 | 204 | if (empty($geom_types)) { 205 | return FALSE; 206 | } 207 | 208 | if (count($geom_types) == 1) { 209 | if (count($geometries) == 1) { 210 | return $geometries[0]; 211 | } 212 | else { 213 | $class = 'Multi'.$geom_types[0]; 214 | return new $class($geometries); 215 | } 216 | } 217 | else { 218 | return new GeometryCollection($geometries); 219 | } 220 | } 221 | 222 | // Detect a format given a value. This function is meant to be SPEEDY. 223 | // It could make a mistake in XML detection if you are mixing or using namespaces in weird ways (ie, KML inside an RSS feed) 224 | static function detectFormat(&$input) { 225 | $mem = fopen('php://memory', 'r+'); 226 | fwrite($mem, $input, 11); // Write 11 bytes - we can detect the vast majority of formats in the first 11 bytes 227 | fseek($mem, 0); 228 | 229 | $bytes = unpack("c*", fread($mem, 11)); 230 | 231 | // If bytes is empty, then we were passed empty input 232 | if (empty($bytes)) return FALSE; 233 | 234 | // First char is a tab, space or carriage-return. trim it and try again 235 | if ($bytes[1] == 9 || $bytes[1] == 10 || $bytes[1] == 32) { 236 | $ltinput = ltrim($input); 237 | return geoPHP::detectFormat($ltinput); 238 | } 239 | 240 | // Detect WKB or EWKB -- first byte is 1 (little endian indicator) 241 | if ($bytes[1] == 1) { 242 | // If SRID byte is TRUE (1), it's EWKB 243 | if ($bytes[5]) return 'ewkb'; 244 | else return 'wkb'; 245 | } 246 | 247 | // Detect HEX encoded WKB or EWKB (PostGIS format) -- first byte is 48, second byte is 49 (hex '01' => first-byte = 1) 248 | if ($bytes[1] == 48 && $bytes[2] == 49) { 249 | // The shortest possible WKB string (LINESTRING EMPTY) is 18 hex-chars (9 encoded bytes) long 250 | // This differentiates it from a geohash, which is always shorter than 18 characters. 251 | if (strlen($input) >= 18) { 252 | //@@TODO: Differentiate between EWKB and WKB -- check hex-char 10 or 11 (SRID bool indicator at encoded byte 5) 253 | return 'ewkb:1'; 254 | } 255 | } 256 | 257 | // Detect GeoJSON - first char starts with { 258 | if ($bytes[1] == 123) { 259 | return 'json'; 260 | } 261 | 262 | // Detect EWKT - first char is S 263 | if ($bytes[1] == 83) { 264 | return 'ewkt'; 265 | } 266 | 267 | // Detect WKT - first char starts with P (80), L (76), M (77), or G (71) 268 | $wkt_chars = array(80, 76, 77, 71); 269 | if (in_array($bytes[1], $wkt_chars)) { 270 | return 'wkt'; 271 | } 272 | 273 | // Detect XML -- first char is < 274 | if ($bytes[1] == 60) { 275 | // grab the first 256 characters 276 | $string = substr($input, 0, 256); 277 | if (strpos($string, 'read($wkb); 36 | 37 | // If there is an SRID, add it to the geometry 38 | if ($srid) { 39 | $geom->setSRID($srid); 40 | } 41 | 42 | return $geom; 43 | } 44 | 45 | /** 46 | * Serialize geometries into an EWKB binary string. 47 | * 48 | * @param Geometry $geometry 49 | * 50 | * @return string The Extended-WKB binary string representation of the input geometries 51 | */ 52 | public function write(Geometry $geometry, $write_as_hex = FALSE) { 53 | // We always write into NDR (little endian) 54 | $wkb = pack('c',1); 55 | 56 | switch ($geometry->getGeomType()) { 57 | case 'Point'; 58 | $wkb .= pack('L',1); 59 | $wkb .= $this->writePoint($geometry); 60 | break; 61 | case 'LineString'; 62 | $wkb .= pack('L',2); 63 | $wkb .= $this->writeLineString($geometry); 64 | break; 65 | case 'Polygon'; 66 | $wkb .= pack('L',3); 67 | $wkb .= $this->writePolygon($geometry); 68 | break; 69 | case 'MultiPoint'; 70 | $wkb .= pack('L',4); 71 | $wkb .= $this->writeMulti($geometry); 72 | break; 73 | case 'MultiLineString'; 74 | $wkb .= pack('L',5); 75 | $wkb .= $this->writeMulti($geometry); 76 | break; 77 | case 'MultiPolygon'; 78 | $wkb .= pack('L',6); 79 | $wkb .= $this->writeMulti($geometry); 80 | break; 81 | case 'GeometryCollection'; 82 | $wkb .= pack('L',7); 83 | $wkb .= $this->writeMulti($geometry); 84 | break; 85 | } 86 | 87 | if ($write_as_hex) { 88 | $unpacked = unpack('H*',$wkb); 89 | return $unpacked[1]; 90 | } 91 | else { 92 | return $wkb; 93 | } 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /lib/adapters/EWKT.class.php: -------------------------------------------------------------------------------- 1 | SRID(); 17 | $wkt = ''; 18 | if ($srid) { 19 | $wkt = 'SRID=' . $srid . ';'; 20 | $wkt .= $geometry->out('wkt'); 21 | return $wkt; 22 | } 23 | else { 24 | return $geometry->out('wkt'); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/adapters/GPX.class.php: -------------------------------------------------------------------------------- 1 | geomFromText($gpx); 27 | } 28 | 29 | /** 30 | * Serialize geometries into a GPX string. 31 | * 32 | * @param Geometry $geometry 33 | * 34 | * @return string The GPX string representation of the input geometries 35 | */ 36 | public function write(Geometry $geometry, $namespace = FALSE) { 37 | if ($geometry->isEmpty()) return NULL; 38 | if ($namespace) { 39 | $this->namespace = $namespace; 40 | $this->nss = $namespace.':'; 41 | } 42 | return '<'.$this->nss.'gpx creator="geoPHP" version="1.0">'.$this->geometryToGPX($geometry).'nss.'gpx>'; 43 | } 44 | 45 | public function geomFromText($text) { 46 | // Change to lower-case and strip all CDATA 47 | $text = strtolower($text); 48 | $text = preg_replace('//s','',$text); 49 | 50 | // Load into DOMDocument 51 | $xmlobj = new DOMDocument(); 52 | @$xmlobj->loadXML($text); 53 | if ($xmlobj === false) { 54 | throw new Exception("Invalid GPX: ". $text); 55 | } 56 | 57 | $this->xmlobj = $xmlobj; 58 | try { 59 | $geom = $this->geomFromXML(); 60 | } catch(InvalidText $e) { 61 | throw new Exception("Cannot Read Geometry From GPX: ". $text); 62 | } catch(Exception $e) { 63 | throw $e; 64 | } 65 | 66 | return $geom; 67 | } 68 | 69 | protected function geomFromXML() { 70 | $geometries = array(); 71 | $geometries = array_merge($geometries, $this->parseWaypoints()); 72 | $geometries = array_merge($geometries, $this->parseTracks()); 73 | $geometries = array_merge($geometries, $this->parseRoutes()); 74 | 75 | if (empty($geometries)) { 76 | throw new Exception("Invalid / Empty GPX"); 77 | } 78 | 79 | return geoPHP::geometryReduce($geometries); 80 | } 81 | 82 | protected function childElements($xml, $nodename = '') { 83 | $children = array(); 84 | foreach ($xml->childNodes as $child) { 85 | if ($child->nodeName == $nodename) { 86 | $children[] = $child; 87 | } 88 | } 89 | return $children; 90 | } 91 | 92 | protected function parseWaypoints() { 93 | $points = array(); 94 | $wpt_elements = $this->xmlobj->getElementsByTagName('wpt'); 95 | foreach ($wpt_elements as $wpt) { 96 | $lat = $wpt->attributes->getNamedItem("lat")->nodeValue; 97 | $lon = $wpt->attributes->getNamedItem("lon")->nodeValue; 98 | $points[] = new Point($lon, $lat); 99 | } 100 | return $points; 101 | } 102 | 103 | protected function parseTracks() { 104 | $lines = array(); 105 | $trk_elements = $this->xmlobj->getElementsByTagName('trk'); 106 | foreach ($trk_elements as $trk) { 107 | $components = array(); 108 | foreach ($this->childElements($trk, 'trkseg') as $trkseg) { 109 | foreach ($this->childElements($trkseg, 'trkpt') as $trkpt) { 110 | $lat = $trkpt->attributes->getNamedItem("lat")->nodeValue; 111 | $lon = $trkpt->attributes->getNamedItem("lon")->nodeValue; 112 | $components[] = new Point($lon, $lat); 113 | } 114 | } 115 | if ($components) {$lines[] = new LineString($components);} 116 | } 117 | return $lines; 118 | } 119 | 120 | protected function parseRoutes() { 121 | $lines = array(); 122 | $rte_elements = $this->xmlobj->getElementsByTagName('rte'); 123 | foreach ($rte_elements as $rte) { 124 | $components = array(); 125 | foreach ($this->childElements($rte, 'rtept') as $rtept) { 126 | $lat = $rtept->attributes->getNamedItem("lat")->nodeValue; 127 | $lon = $rtept->attributes->getNamedItem("lon")->nodeValue; 128 | $components[] = new Point($lon, $lat); 129 | } 130 | $lines[] = new LineString($components); 131 | } 132 | return $lines; 133 | } 134 | 135 | protected function geometryToGPX($geom) { 136 | $type = strtolower($geom->getGeomType()); 137 | switch ($type) { 138 | case 'point': 139 | return $this->pointToGPX($geom); 140 | break; 141 | case 'linestring': 142 | return $this->linestringToGPX($geom); 143 | break; 144 | case 'polygon': 145 | case 'multipoint': 146 | case 'multilinestring': 147 | case 'multipolygon': 148 | case 'geometrycollection': 149 | return $this->collectionToGPX($geom); 150 | break; 151 | } 152 | } 153 | 154 | private function pointToGPX($geom) { 155 | return '<'.$this->nss.'wpt lat="'.$geom->getY().'" lon="'.$geom->getX().'" />'; 156 | } 157 | 158 | private function linestringToGPX($geom) { 159 | $gpx = '<'.$this->nss.'trk><'.$this->nss.'trkseg>'; 160 | 161 | foreach ($geom->getComponents() as $comp) { 162 | $gpx .= '<'.$this->nss.'trkpt lat="'.$comp->getY().'" lon="'.$comp->getX().'" />'; 163 | } 164 | 165 | $gpx .= 'nss.'trkseg>nss.'trk>'; 166 | 167 | return $gpx; 168 | } 169 | 170 | public function collectionToGPX($geom) { 171 | $gpx = ''; 172 | $components = $geom->getComponents(); 173 | foreach ($geom->getComponents() as $comp) { 174 | $gpx .= $this->geometryToGPX($comp); 175 | } 176 | 177 | return $gpx; 178 | } 179 | 180 | } 181 | -------------------------------------------------------------------------------- /lib/adapters/GeoAdapter.class.php: -------------------------------------------------------------------------------- 1 | array ( 22 | 'even' => 'p0r21436x8zb9dcf5h7kjnmqesgutwvy', 23 | 'odd' => 'bc01fg45238967deuvhjyznpkmstqrwx' 24 | ), 25 | // east 26 | 'right' => array ( 27 | 'even' => 'bc01fg45238967deuvhjyznpkmstqrwx', 28 | 'odd' => 'p0r21436x8zb9dcf5h7kjnmqesgutwvy' 29 | ), 30 | // west 31 | 'left' => array ( 32 | 'even' => '238967debc01fg45kmstqrwxuvhjyznp', 33 | 'odd' => '14365h7k9dcfesgujnmqp0r2twvyx8zb' 34 | ), 35 | // south 36 | 'bottom' => array ( 37 | 'even' => '14365h7k9dcfesgujnmqp0r2twvyx8zb', 38 | 'odd' => '238967debc01fg45kmstqrwxuvhjyznp' 39 | ) 40 | ); 41 | 42 | /** 43 | * array of bordering hash character maps. 44 | */ 45 | private $borders = array ( 46 | // north 47 | 'top' => array ( 48 | 'even' => 'prxz', 49 | 'odd' => 'bcfguvyz' 50 | ), 51 | // east 52 | 'right' => array ( 53 | 'even' => 'bcfguvyz', 54 | 'odd' => 'prxz' 55 | ), 56 | // west 57 | 'left' => array ( 58 | 'even' => '0145hjnp', 59 | 'odd' => '028b' 60 | ), 61 | // south 62 | 'bottom' => array ( 63 | 'even' => '028b', 64 | 'odd' => '0145hjnp' 65 | ) 66 | ); 67 | 68 | /** 69 | * Convert the geohash to a Point. The point is 2-dimensional. 70 | * @return Point the converted geohash 71 | * @param string $hash a geohash 72 | * @see GeoAdapter::read() 73 | */ 74 | public function read($hash, $as_grid = FALSE) { 75 | $ll = $this->decode($hash); 76 | if (!$as_grid) { 77 | return new Point($ll['medlon'], $ll['medlat']); 78 | } 79 | else { 80 | return new Polygon(array( 81 | new LineString(array( 82 | new Point($ll['minlon'], $ll['maxlat']), 83 | new Point($ll['maxlon'], $ll['maxlat']), 84 | new Point($ll['maxlon'], $ll['minlat']), 85 | new Point($ll['minlon'], $ll['minlat']), 86 | new Point($ll['minlon'], $ll['maxlat']), 87 | )) 88 | )); 89 | } 90 | } 91 | 92 | /** 93 | * Convert the geometry to geohash. 94 | * @return string the geohash or null when the $geometry is not a Point 95 | * @param Point $geometry 96 | * @see GeoAdapter::write() 97 | */ 98 | public function write(Geometry $geometry, $precision = NULL){ 99 | if ($geometry->isEmpty()) return ''; 100 | 101 | if($geometry->geometryType() === 'Point'){ 102 | return $this->encodePoint($geometry, $precision); 103 | } 104 | else { 105 | // The geohash is the hash grid ID that fits the envelope 106 | $envelope = $geometry->envelope(); 107 | $geohashes = array(); 108 | $geohash = ''; 109 | foreach ($envelope->getPoints() as $point) { 110 | $geohashes[] = $this->encodePoint($point, 0.0000001); 111 | } 112 | $i = 0; 113 | while ($i < strlen($geohashes[0])) { 114 | $char = $geohashes[0][$i]; 115 | foreach ($geohashes as $hash) { 116 | if ($hash[$i] != $char) { 117 | return $geohash; 118 | } 119 | } 120 | $geohash .= $char; 121 | $i++; 122 | } 123 | return $geohash; 124 | } 125 | } 126 | 127 | /** 128 | * @return string geohash 129 | * @param Point $point 130 | * @author algorithm based on code by Alexander Songe 131 | * @see https://github.com/asonge/php-geohash/issues/1 132 | */ 133 | private function encodePoint($point, $precision = NULL){ 134 | if ($precision === NULL) { 135 | $lap = strlen($point->y())-strpos($point->y(),"."); 136 | $lop = strlen($point->x())-strpos($point->x(),"."); 137 | $precision = pow(10,-max($lap-1,$lop-1,0))/2; 138 | } 139 | 140 | $minlat = -90; 141 | $maxlat = 90; 142 | $minlon = -180; 143 | $maxlon = 180; 144 | $latE = 90; 145 | $lonE = 180; 146 | $i = 0; 147 | $error = 180; 148 | $hash=''; 149 | while($error>=$precision) { 150 | $chr = 0; 151 | for($b=4;$b>=0;--$b) { 152 | if((1&$b) == (1&$i)) { 153 | // even char, even bit OR odd char, odd bit...a lon 154 | $next = ($minlon+$maxlon)/2; 155 | if($point->x()>$next) { 156 | $chr |= pow(2,$b); 157 | $minlon = $next; 158 | } else { 159 | $maxlon = $next; 160 | } 161 | $lonE /= 2; 162 | } else { 163 | // odd char, even bit OR even char, odd bit...a lat 164 | $next = ($minlat+$maxlat)/2; 165 | if($point->y()>$next) { 166 | $chr |= pow(2,$b); 167 | $minlat = $next; 168 | } else { 169 | $maxlat = $next; 170 | } 171 | $latE /= 2; 172 | } 173 | } 174 | $hash .= $this->table[$chr]; 175 | $i++; 176 | $error = min($latE,$lonE); 177 | } 178 | return $hash; 179 | } 180 | 181 | /** 182 | * @param string $hash a geohash 183 | * @author algorithm based on code by Alexander Songe 184 | * @see https://github.com/asonge/php-geohash/issues/1 185 | */ 186 | private function decode($hash){ 187 | $ll = array(); 188 | $minlat = -90; 189 | $maxlat = 90; 190 | $minlon = -180; 191 | $maxlon = 180; 192 | $latE = 90; 193 | $lonE = 180; 194 | for($i=0,$c=strlen($hash);$i<$c;$i++) { 195 | $v = strpos($this->table,$hash[$i]); 196 | if(1&$i) { 197 | if(16&$v)$minlat = ($minlat+$maxlat)/2; else $maxlat = ($minlat+$maxlat)/2; 198 | if(8&$v) $minlon = ($minlon+$maxlon)/2; else $maxlon = ($minlon+$maxlon)/2; 199 | if(4&$v) $minlat = ($minlat+$maxlat)/2; else $maxlat = ($minlat+$maxlat)/2; 200 | if(2&$v) $minlon = ($minlon+$maxlon)/2; else $maxlon = ($minlon+$maxlon)/2; 201 | if(1&$v) $minlat = ($minlat+$maxlat)/2; else $maxlat = ($minlat+$maxlat)/2; 202 | $latE /= 8; 203 | $lonE /= 4; 204 | } else { 205 | if(16&$v)$minlon = ($minlon+$maxlon)/2; else $maxlon = ($minlon+$maxlon)/2; 206 | if(8&$v) $minlat = ($minlat+$maxlat)/2; else $maxlat = ($minlat+$maxlat)/2; 207 | if(4&$v) $minlon = ($minlon+$maxlon)/2; else $maxlon = ($minlon+$maxlon)/2; 208 | if(2&$v) $minlat = ($minlat+$maxlat)/2; else $maxlat = ($minlat+$maxlat)/2; 209 | if(1&$v) $minlon = ($minlon+$maxlon)/2; else $maxlon = ($minlon+$maxlon)/2; 210 | $latE /= 4; 211 | $lonE /= 8; 212 | } 213 | } 214 | $ll['minlat'] = $minlat; 215 | $ll['minlon'] = $minlon; 216 | $ll['maxlat'] = $maxlat; 217 | $ll['maxlon'] = $maxlon; 218 | $ll['medlat'] = round(($minlat+$maxlat)/2, max(1, -round(log10($latE)))-1); 219 | $ll['medlon'] = round(($minlon+$maxlon)/2, max(1, -round(log10($lonE)))-1); 220 | return $ll; 221 | } 222 | 223 | /** 224 | * Calculates the adjacent geohash of the geohash in the specified direction. 225 | * This algorithm is available in various ports that seem to point back to 226 | * geohash-js by David Troy under MIT notice. 227 | * 228 | * 229 | * @see https://github.com/davetroy/geohash-js 230 | * @see https://github.com/lyokato/objc-geohash 231 | * @see https://github.com/lyokato/libgeohash 232 | * @see https://github.com/masuidrive/pr_geohash 233 | * @see https://github.com/sunng87/node-geohash 234 | * @see https://github.com/davidmoten/geo 235 | * 236 | * @param string $hash the geohash (lowercase) 237 | * @param string $direction the direction of the neighbor (top, bottom, left or right) 238 | * @return string the geohash of the adjacent cell 239 | */ 240 | public function adjacent($hash, $direction){ 241 | $last = substr($hash, -1); 242 | $type = (strlen($hash) % 2)? 'odd': 'even'; 243 | $base = substr($hash, 0, strlen($hash) - 1); 244 | if(strpos(($this->borders[$direction][$type]), $last) !== false){ 245 | $base = $this->adjacent($base, $direction); 246 | } 247 | return $base.$this->table[strpos($this->neighbours[$direction][$type], $last)]; 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /lib/adapters/GeoJSON.class.php: -------------------------------------------------------------------------------- 1 | type)) { 26 | throw new Exception('Invalid JSON'); 27 | } 28 | 29 | // Check to see if it's a FeatureCollection 30 | if ($input->type == 'FeatureCollection') { 31 | $geoms = array(); 32 | foreach ($input->features as $feature) { 33 | $geoms[] = $this->read($feature); 34 | } 35 | return geoPHP::geometryReduce($geoms); 36 | } 37 | 38 | // Check to see if it's a Feature 39 | if ($input->type == 'Feature') { 40 | return $this->read($input->geometry); 41 | } 42 | 43 | // It's a geometry - process it 44 | return $this->objToGeom($input); 45 | } 46 | 47 | private function objToGeom($obj) { 48 | $type = $obj->type; 49 | 50 | if ($type == 'GeometryCollection') { 51 | return $this->objToGeometryCollection($obj); 52 | } 53 | $method = 'arrayTo' . $type; 54 | return $this->$method($obj->coordinates); 55 | } 56 | 57 | private function arrayToPoint($array) { 58 | if (!empty($array)) { 59 | return new Point($array[0], $array[1]); 60 | } 61 | else { 62 | return new Point(); 63 | } 64 | } 65 | 66 | private function arrayToLineString($array) { 67 | $points = array(); 68 | foreach ($array as $comp_array) { 69 | $points[] = $this->arrayToPoint($comp_array); 70 | } 71 | return new LineString($points); 72 | } 73 | 74 | private function arrayToPolygon($array) { 75 | $lines = array(); 76 | foreach ($array as $comp_array) { 77 | $lines[] = $this->arrayToLineString($comp_array); 78 | } 79 | return new Polygon($lines); 80 | } 81 | 82 | private function arrayToMultiPoint($array) { 83 | $points = array(); 84 | foreach ($array as $comp_array) { 85 | $points[] = $this->arrayToPoint($comp_array); 86 | } 87 | return new MultiPoint($points); 88 | } 89 | 90 | private function arrayToMultiLineString($array) { 91 | $lines = array(); 92 | foreach ($array as $comp_array) { 93 | $lines[] = $this->arrayToLineString($comp_array); 94 | } 95 | return new MultiLineString($lines); 96 | } 97 | 98 | private function arrayToMultiPolygon($array) { 99 | $polys = array(); 100 | foreach ($array as $comp_array) { 101 | $polys[] = $this->arrayToPolygon($comp_array); 102 | } 103 | return new MultiPolygon($polys); 104 | } 105 | 106 | private function objToGeometryCollection($obj) { 107 | $geoms = array(); 108 | if (empty($obj->geometries)) { 109 | throw new Exception('Invalid GeoJSON: GeometryCollection with no component geometries'); 110 | } 111 | foreach ($obj->geometries as $comp_object) { 112 | $geoms[] = $this->objToGeom($comp_object); 113 | } 114 | return new GeometryCollection($geoms); 115 | } 116 | 117 | /** 118 | * Serializes an object into a geojson string 119 | * 120 | * 121 | * @param Geometry $obj The object to serialize 122 | * 123 | * @return string The GeoJSON string 124 | */ 125 | public function write(Geometry $geometry, $return_array = FALSE) { 126 | if ($return_array) { 127 | return $this->getArray($geometry); 128 | } 129 | else { 130 | return json_encode($this->getArray($geometry)); 131 | } 132 | } 133 | 134 | public function getArray($geometry) { 135 | if ($geometry->getGeomType() == 'GeometryCollection') { 136 | $component_array = array(); 137 | foreach ($geometry->components as $component) { 138 | $component_array[] = array( 139 | 'type' => $component->geometryType(), 140 | 'coordinates' => $component->asArray(), 141 | ); 142 | } 143 | return array( 144 | 'type'=> 'GeometryCollection', 145 | 'geometries'=> $component_array, 146 | ); 147 | } 148 | else return array( 149 | 'type'=> $geometry->getGeomType(), 150 | 'coordinates'=> $geometry->asArray(), 151 | ); 152 | } 153 | } 154 | 155 | 156 | -------------------------------------------------------------------------------- /lib/adapters/GeoRSS.class.php: -------------------------------------------------------------------------------- 1 | geomFromText($gpx); 27 | } 28 | 29 | /** 30 | * Serialize geometries into a GeoRSS string. 31 | * 32 | * @param Geometry $geometry 33 | * 34 | * @return string The georss string representation of the input geometries 35 | */ 36 | public function write(Geometry $geometry, $namespace = FALSE) { 37 | if ($namespace) { 38 | $this->namespace = $namespace; 39 | $this->nss = $namespace.':'; 40 | } 41 | return $this->geometryToGeoRSS($geometry); 42 | } 43 | 44 | public function geomFromText($text) { 45 | // Change to lower-case, strip all CDATA, and de-namespace 46 | $text = strtolower($text); 47 | $text = preg_replace('//s','',$text); 48 | 49 | // Load into DOMDOcument 50 | $xmlobj = new DOMDocument(); 51 | @$xmlobj->loadXML($text); 52 | if ($xmlobj === false) { 53 | throw new Exception("Invalid GeoRSS: ". $text); 54 | } 55 | 56 | $this->xmlobj = $xmlobj; 57 | try { 58 | $geom = $this->geomFromXML(); 59 | } catch(InvalidText $e) { 60 | throw new Exception("Cannot Read Geometry From GeoRSS: ". $text); 61 | } catch(Exception $e) { 62 | throw $e; 63 | } 64 | 65 | return $geom; 66 | } 67 | 68 | protected function geomFromXML() { 69 | $geometries = array(); 70 | $geometries = array_merge($geometries, $this->parsePoints()); 71 | $geometries = array_merge($geometries, $this->parseLines()); 72 | $geometries = array_merge($geometries, $this->parsePolygons()); 73 | $geometries = array_merge($geometries, $this->parseBoxes()); 74 | $geometries = array_merge($geometries, $this->parseCircles()); 75 | 76 | if (empty($geometries)) { 77 | throw new Exception("Invalid / Empty GeoRSS"); 78 | } 79 | 80 | return geoPHP::geometryReduce($geometries); 81 | } 82 | 83 | protected function getPointsFromCoords($string) { 84 | $coords = array(); 85 | 86 | if (empty($string)) { 87 | return $coords; 88 | } 89 | 90 | $latlon = explode(' ',$string); 91 | foreach ($latlon as $key => $item) { 92 | if (!($key % 2)) { 93 | // It's a latitude 94 | $lat = $item; 95 | } 96 | else { 97 | // It's a longitude 98 | $lon = $item; 99 | $coords[] = new Point($lon, $lat); 100 | } 101 | } 102 | return $coords; 103 | } 104 | 105 | protected function parsePoints() { 106 | $points = array(); 107 | $pt_elements = $this->xmlobj->getElementsByTagName('point'); 108 | foreach ($pt_elements as $pt) { 109 | if ($pt->hasChildNodes()) { 110 | $point_array = $this->getPointsFromCoords(trim($pt->firstChild->nodeValue)); 111 | } 112 | if (!empty($point_array)) { 113 | $points[] = $point_array[0]; 114 | } 115 | else { 116 | $points[] = new Point(); 117 | } 118 | } 119 | return $points; 120 | } 121 | 122 | protected function parseLines() { 123 | $lines = array(); 124 | $line_elements = $this->xmlobj->getElementsByTagName('line'); 125 | foreach ($line_elements as $line) { 126 | $components = $this->getPointsFromCoords(trim($line->firstChild->nodeValue)); 127 | $lines[] = new LineString($components); 128 | } 129 | return $lines; 130 | } 131 | 132 | protected function parsePolygons() { 133 | $polygons = array(); 134 | $poly_elements = $this->xmlobj->getElementsByTagName('polygon'); 135 | foreach ($poly_elements as $poly) { 136 | if ($poly->hasChildNodes()) { 137 | $points = $this->getPointsFromCoords(trim($poly->firstChild->nodeValue)); 138 | $exterior_ring = new LineString($points); 139 | $polygons[] = new Polygon(array($exterior_ring)); 140 | } 141 | else { 142 | // It's an EMPTY polygon 143 | $polygons[] = new Polygon(); 144 | } 145 | } 146 | return $polygons; 147 | } 148 | 149 | // Boxes are rendered into polygons 150 | protected function parseBoxes() { 151 | $polygons = array(); 152 | $box_elements = $this->xmlobj->getElementsByTagName('box'); 153 | foreach ($box_elements as $box) { 154 | $parts = explode(' ',trim($box->firstChild->nodeValue)); 155 | $components = array( 156 | new Point($parts[3], $parts[2]), 157 | new Point($parts[3], $parts[0]), 158 | new Point($parts[1], $parts[0]), 159 | new Point($parts[1], $parts[2]), 160 | new Point($parts[3], $parts[2]), 161 | ); 162 | $exterior_ring = new LineString($components); 163 | $polygons[] = new Polygon(array($exterior_ring)); 164 | } 165 | return $polygons; 166 | } 167 | 168 | // Circles are rendered into points 169 | // @@TODO: Add good support once we have circular-string geometry support 170 | protected function parseCircles() { 171 | $points = array(); 172 | $circle_elements = $this->xmlobj->getElementsByTagName('circle'); 173 | foreach ($circle_elements as $circle) { 174 | $parts = explode(' ',trim($circle->firstChild->nodeValue)); 175 | $points[] = new Point($parts[1], $parts[0]); 176 | } 177 | return $points; 178 | } 179 | 180 | protected function geometryToGeoRSS($geom) { 181 | $type = strtolower($geom->getGeomType()); 182 | switch ($type) { 183 | case 'point': 184 | return $this->pointToGeoRSS($geom); 185 | break; 186 | case 'linestring': 187 | return $this->linestringToGeoRSS($geom); 188 | break; 189 | case 'polygon': 190 | return $this->PolygonToGeoRSS($geom); 191 | break; 192 | case 'multipoint': 193 | case 'multilinestring': 194 | case 'multipolygon': 195 | case 'geometrycollection': 196 | return $this->collectionToGeoRSS($geom); 197 | break; 198 | } 199 | return $output; 200 | } 201 | 202 | private function pointToGeoRSS($geom) { 203 | $out = '<'.$this->nss.'point>'; 204 | if (!$geom->isEmpty()) { 205 | $out .= $geom->getY().' '.$geom->getX(); 206 | } 207 | $out .= 'nss.'point>'; 208 | return $out; 209 | } 210 | 211 | private function linestringToGeoRSS($geom) { 212 | $output = '<'.$this->nss.'line>'; 213 | foreach ($geom->getComponents() as $k => $point) { 214 | $output .= $point->getY().' '.$point->getX(); 215 | if ($k < ($geom->numGeometries() -1)) $output .= ' '; 216 | } 217 | $output .= 'nss.'line>'; 218 | return $output; 219 | } 220 | 221 | private function polygonToGeoRSS($geom) { 222 | $output = '<'.$this->nss.'polygon>'; 223 | $exterior_ring = $geom->exteriorRing(); 224 | foreach ($exterior_ring->getComponents() as $k => $point) { 225 | $output .= $point->getY().' '.$point->getX(); 226 | if ($k < ($exterior_ring->numGeometries() -1)) $output .= ' '; 227 | } 228 | $output .= 'nss.'polygon>'; 229 | return $output; 230 | } 231 | 232 | public function collectionToGeoRSS($geom) { 233 | $georss = '<'.$this->nss.'where>'; 234 | $components = $geom->getComponents(); 235 | foreach ($geom->getComponents() as $comp) { 236 | $georss .= $this->geometryToGeoRSS($comp); 237 | } 238 | 239 | $georss .= 'nss.'where>'; 240 | 241 | return $georss; 242 | } 243 | 244 | } 245 | -------------------------------------------------------------------------------- /lib/adapters/GoogleGeocode.class.php: -------------------------------------------------------------------------------- 1 | 4 | * (c) Patrick Hayes 5 | * 6 | * This code is open-source and licenced under the Modified BSD License. 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | /** 12 | * PHP Google Geocoder Adapter 13 | * 14 | * 15 | * @package geoPHP 16 | * @author Patrick Hayes 17 | */ 18 | class GoogleGeocode extends GeoAdapter 19 | { 20 | 21 | /** 22 | * Read an address string or array geometry objects 23 | * 24 | * @param string - Address to geocode 25 | * @param string - Type of Geometry to return. Can either be 'points' or 'bounds' (polygon) 26 | * @param Geometry|bounds-array - Limit the search area to within this region. For example 27 | * by default geocoding "Cairo" will return the location of Cairo Egypt. 28 | * If you pass a polygon of illinois, it will return Cairo IL. 29 | * @param return_multiple - Return all results in a multipoint or multipolygon 30 | * @return Geometry|GeometryCollection 31 | */ 32 | public function read($address, $return_type = 'point', $bounds = FALSE, $return_multiple = FALSE) { 33 | if (is_array($address)) $address = join(',', $address); 34 | 35 | if (gettype($bounds) == 'object') { 36 | $bounds = $bounds->getBBox(); 37 | } 38 | if (gettype($bounds) == 'array') { 39 | $bounds_string = '&bounds='.$bounds['miny'].','.$bounds['minx'].'|'.$bounds['maxy'].','.$bounds['maxx']; 40 | } 41 | else { 42 | $bounds_string = ''; 43 | } 44 | 45 | $url = "http://maps.googleapis.com/maps/api/geocode/json"; 46 | $url .= '?address='. urlencode($address); 47 | $url .= $bounds_string; 48 | $url .= '&sensor=false'; 49 | $this->result = json_decode(@file_get_contents($url)); 50 | 51 | if ($this->result->status == 'OK') { 52 | if ($return_multiple == FALSE) { 53 | if ($return_type == 'point') { 54 | return $this->getPoint(); 55 | } 56 | if ($return_type == 'bounds' || $return_type == 'polygon') { 57 | return $this->getPolygon(); 58 | } 59 | } 60 | if ($return_multiple == TRUE) { 61 | if ($return_type == 'point') { 62 | $points = array(); 63 | foreach ($this->result->results as $delta => $item) { 64 | $points[] = $this->getPoint($delta); 65 | } 66 | return new MultiPoint($points); 67 | } 68 | if ($return_type == 'bounds' || $return_type == 'polygon') { 69 | $polygons = array(); 70 | foreach ($this->result->results as $delta => $item) { 71 | $polygons[] = $this->getPolygon($delta); 72 | } 73 | return new MultiPolygon($polygons); 74 | } 75 | } 76 | } 77 | else { 78 | if ($this->result->status) throw new Exception('Error in Google Geocoder: '.$this->result->status); 79 | else throw new Exception('Unknown error in Google Geocoder'); 80 | return FALSE; 81 | } 82 | } 83 | 84 | /** 85 | * Serialize geometries into a WKT string. 86 | * 87 | * @param Geometry $geometry 88 | * @param string $return_type Should be either 'string' or 'array' 89 | * 90 | * @return string Does a reverse geocode of the geometry 91 | */ 92 | public function write(Geometry $geometry, $return_type = 'string') { 93 | $centroid = $geometry->getCentroid(); 94 | $lat = $centroid->getY(); 95 | $lon = $centroid->getX(); 96 | 97 | $url = "http://maps.googleapis.com/maps/api/geocode/json"; 98 | $url .= '?latlng='.$lat.','.$lon; 99 | $url .= '&sensor=false'; 100 | $this->result = json_decode(@file_get_contents($url)); 101 | 102 | if ($this->result->status == 'OK') { 103 | if ($return_type == 'string') { 104 | return $this->result->results[0]->formatted_address; 105 | } 106 | if ($return_type == 'array') { 107 | return $this->result->results[0]->address_components; 108 | } 109 | } 110 | elseif ($this->result->status == 'ZERO_RESULTS') { 111 | if ($return_type == 'string') { 112 | return ''; 113 | } 114 | if ($return_type == 'array') { 115 | return $this->result->results; 116 | } 117 | } 118 | else { 119 | if ($this->result->status) throw new Exception('Error in Google Reverse Geocoder: '.$this->result->status); 120 | else throw new Exception('Unknown error in Google Reverse Geocoder'); 121 | return FALSE; 122 | } 123 | } 124 | 125 | private function getPoint($delta = 0) { 126 | $lat = $this->result->results[$delta]->geometry->location->lat; 127 | $lon = $this->result->results[$delta]->geometry->location->lng; 128 | return new Point($lon, $lat); 129 | } 130 | 131 | private function getPolygon($delta = 0) { 132 | $points = array ( 133 | $this->getTopLeft($delta), 134 | $this->getTopRight($delta), 135 | $this->getBottomRight($delta), 136 | $this->getBottomLeft($delta), 137 | $this->getTopLeft($delta), 138 | ); 139 | $outer_ring = new LineString($points); 140 | return new Polygon(array($outer_ring)); 141 | } 142 | 143 | private function getTopLeft($delta = 0) { 144 | $lat = $this->result->results[$delta]->geometry->bounds->northeast->lat; 145 | $lon = $this->result->results[$delta]->geometry->bounds->southwest->lng; 146 | return new Point($lon, $lat); 147 | } 148 | 149 | private function getTopRight($delta = 0) { 150 | $lat = $this->result->results[$delta]->geometry->bounds->northeast->lat; 151 | $lon = $this->result->results[$delta]->geometry->bounds->northeast->lng; 152 | return new Point($lon, $lat); 153 | } 154 | 155 | private function getBottomLeft($delta = 0) { 156 | $lat = $this->result->results[$delta]->geometry->bounds->southwest->lat; 157 | $lon = $this->result->results[$delta]->geometry->bounds->southwest->lng; 158 | return new Point($lon, $lat); 159 | } 160 | 161 | private function getBottomRight($delta = 0) { 162 | $lat = $this->result->results[$delta]->geometry->bounds->southwest->lat; 163 | $lon = $this->result->results[$delta]->geometry->bounds->northeast->lng; 164 | return new Point($lon, $lat); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /lib/adapters/KML.class.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | class KML extends GeoAdapter 22 | { 23 | private $namespace = FALSE; 24 | private $nss = ''; // Name-space string. eg 'georss:' 25 | 26 | /** 27 | * Read KML string into geometry objects 28 | * 29 | * @param string $kml A KML string 30 | * 31 | * @return Geometry|GeometryCollection 32 | */ 33 | public function read($kml) { 34 | return $this->geomFromText($kml); 35 | } 36 | 37 | /** 38 | * Serialize geometries into a KML string. 39 | * 40 | * @param Geometry $geometry 41 | * 42 | * @return string The KML string representation of the input geometries 43 | */ 44 | public function write(Geometry $geometry, $namespace = FALSE) { 45 | if ($namespace) { 46 | $this->namespace = $namespace; 47 | $this->nss = $namespace.':'; 48 | } 49 | return $this->geometryToKML($geometry); 50 | } 51 | 52 | public function geomFromText($text) { 53 | // Change to lower-case and strip all CDATA 54 | $text = mb_strtolower($text, mb_detect_encoding($text)); 55 | $text = preg_replace('//s','',$text); 56 | 57 | // Load into DOMDocument 58 | $xmlobj = new DOMDocument(); 59 | @$xmlobj->loadXML($text); 60 | if ($xmlobj === false) { 61 | throw new Exception("Invalid KML: ". $text); 62 | } 63 | 64 | $this->xmlobj = $xmlobj; 65 | try { 66 | $geom = $this->geomFromXML(); 67 | } catch(InvalidText $e) { 68 | throw new Exception("Cannot Read Geometry From KML: ". $text); 69 | } catch(Exception $e) { 70 | throw $e; 71 | } 72 | 73 | return $geom; 74 | } 75 | 76 | protected function geomFromXML() { 77 | $geometries = array(); 78 | $geom_types = geoPHP::geometryList(); 79 | $placemark_elements = $this->xmlobj->getElementsByTagName('placemark'); 80 | if ($placemark_elements->length) { 81 | foreach ($placemark_elements as $placemark) { 82 | foreach ($placemark->childNodes as $child) { 83 | // Node names are all the same, except for MultiGeometry, which maps to GeometryCollection 84 | $node_name = $child->nodeName == 'multigeometry' ? 'geometrycollection' : $child->nodeName; 85 | if (array_key_exists($node_name, $geom_types)) { 86 | $function = 'parse'.$geom_types[$node_name]; 87 | $geometries[] = $this->$function($child); 88 | } 89 | } 90 | } 91 | } 92 | else { 93 | // The document does not have a placemark, try to create a valid geometry from the root element 94 | $node_name = $this->xmlobj->documentElement->nodeName == 'multigeometry' ? 'geometrycollection' : $this->xmlobj->documentElement->nodeName; 95 | if (array_key_exists($node_name, $geom_types)) { 96 | $function = 'parse'.$geom_types[$node_name]; 97 | $geometries[] = $this->$function($this->xmlobj->documentElement); 98 | } 99 | } 100 | return geoPHP::geometryReduce($geometries); 101 | } 102 | 103 | protected function childElements($xml, $nodename = '') { 104 | $children = array(); 105 | if ($xml->childNodes) { 106 | foreach ($xml->childNodes as $child) { 107 | if ($child->nodeName == $nodename) { 108 | $children[] = $child; 109 | } 110 | } 111 | } 112 | return $children; 113 | } 114 | 115 | protected function parsePoint($xml) { 116 | $coordinates = $this->_extractCoordinates($xml); 117 | if (!empty($coordinates)) { 118 | return new Point($coordinates[0][0],$coordinates[0][1]); 119 | } 120 | else { 121 | return new Point(); 122 | } 123 | } 124 | 125 | protected function parseLineString($xml) { 126 | $coordinates = $this->_extractCoordinates($xml); 127 | $point_array = array(); 128 | foreach ($coordinates as $set) { 129 | $point_array[] = new Point($set[0],$set[1]); 130 | } 131 | return new LineString($point_array); 132 | } 133 | 134 | protected function parsePolygon($xml) { 135 | $components = array(); 136 | 137 | $outer_boundary_element_a = $this->childElements($xml, 'outerboundaryis'); 138 | if (empty($outer_boundary_element_a)) { 139 | return new Polygon(); // It's an empty polygon 140 | } 141 | $outer_boundary_element = $outer_boundary_element_a[0]; 142 | $outer_ring_element_a = $this->childElements($outer_boundary_element, 'linearring'); 143 | $outer_ring_element = $outer_ring_element_a[0]; 144 | $components[] = $this->parseLineString($outer_ring_element); 145 | 146 | if (count($components) != 1) { 147 | throw new Exception("Invalid KML"); 148 | } 149 | 150 | $inner_boundary_element_a = $this->childElements($xml, 'innerboundaryis'); 151 | if (count($inner_boundary_element_a)) { 152 | foreach ($inner_boundary_element_a as $inner_boundary_element) { 153 | foreach ($this->childElements($inner_boundary_element, 'linearring') as $inner_ring_element) { 154 | $components[] = $this->parseLineString($inner_ring_element); 155 | } 156 | } 157 | } 158 | 159 | return new Polygon($components); 160 | } 161 | 162 | protected function parseGeometryCollection($xml) { 163 | $components = array(); 164 | $geom_types = geoPHP::geometryList(); 165 | foreach ($xml->childNodes as $child) { 166 | $nodeName = ($child->nodeName == 'linearring') ? 'linestring' : $child->nodeName; 167 | if (array_key_exists($nodeName, $geom_types)) { 168 | $function = 'parse'.$geom_types[$nodeName]; 169 | $components[] = $this->$function($child); 170 | } 171 | } 172 | return new GeometryCollection($components); 173 | } 174 | 175 | protected function _extractCoordinates($xml) { 176 | $coord_elements = $this->childElements($xml, 'coordinates'); 177 | $coordinates = array(); 178 | if (count($coord_elements)) { 179 | $coord_sets = explode(' ', preg_replace('/[\r\n]+/', ' ', $coord_elements[0]->nodeValue)); 180 | foreach ($coord_sets as $set_string) { 181 | $set_string = trim($set_string); 182 | if ($set_string) { 183 | $set_array = explode(',',$set_string); 184 | if (count($set_array) >= 2) { 185 | $coordinates[] = $set_array; 186 | } 187 | } 188 | } 189 | } 190 | 191 | return $coordinates; 192 | } 193 | 194 | private function geometryToKML($geom) { 195 | $type = strtolower($geom->getGeomType()); 196 | switch ($type) { 197 | case 'point': 198 | return $this->pointToKML($geom); 199 | break; 200 | case 'linestring': 201 | return $this->linestringToKML($geom); 202 | break; 203 | case 'polygon': 204 | return $this->polygonToKML($geom); 205 | break; 206 | case 'multipoint': 207 | case 'multilinestring': 208 | case 'multipolygon': 209 | case 'geometrycollection': 210 | return $this->collectionToKML($geom); 211 | break; 212 | } 213 | } 214 | 215 | private function pointToKML($geom) { 216 | $out = '<'.$this->nss.'Point>'; 217 | if (!$geom->isEmpty()) { 218 | $out .= '<'.$this->nss.'coordinates>'.$geom->getX().",".$geom->getY().'nss.'coordinates>'; 219 | } 220 | $out .= 'nss.'Point>'; 221 | return $out; 222 | } 223 | 224 | private function linestringToKML($geom, $type = FALSE) { 225 | if (!$type) { 226 | $type = $geom->getGeomType(); 227 | } 228 | 229 | $str = '<'.$this->nss . $type .'>'; 230 | 231 | if (!$geom->isEmpty()) { 232 | $str .= '<'.$this->nss.'coordinates>'; 233 | $i=0; 234 | foreach ($geom->getComponents() as $comp) { 235 | if ($i != 0) $str .= ' '; 236 | $str .= $comp->getX() .','. $comp->getY(); 237 | $i++; 238 | } 239 | 240 | $str .= 'nss.'coordinates>'; 241 | } 242 | 243 | $str .= 'nss . $type .'>'; 244 | 245 | return $str; 246 | } 247 | 248 | public function polygonToKML($geom) { 249 | $components = $geom->getComponents(); 250 | $str = ''; 251 | if (!empty($components)) { 252 | $str = '<'.$this->nss.'outerBoundaryIs>' . $this->linestringToKML($components[0], 'LinearRing') . 'nss.'outerBoundaryIs>'; 253 | foreach (array_slice($components, 1) as $comp) { 254 | $str .= '<'.$this->nss.'innerBoundaryIs>' . $this->linestringToKML($comp) . 'nss.'innerBoundaryIs>'; 255 | } 256 | } 257 | 258 | return '<'.$this->nss.'Polygon>'. $str .'nss.'Polygon>'; 259 | } 260 | 261 | public function collectionToKML($geom) { 262 | $components = $geom->getComponents(); 263 | $str = '<'.$this->nss.'MultiGeometry>'; 264 | foreach ($geom->getComponents() as $comp) { 265 | $sub_adapter = new KML(); 266 | $str .= $sub_adapter->write($comp); 267 | } 268 | 269 | return $str .'nss.'MultiGeometry>'; 270 | } 271 | 272 | } 273 | -------------------------------------------------------------------------------- /lib/adapters/WKB.class.php: -------------------------------------------------------------------------------- 1 | getGeometry($mem); 45 | fclose($mem); 46 | return $geometry; 47 | } 48 | 49 | function getGeometry(&$mem) { 50 | $base_info = unpack("corder/ctype/cz/cm/cs", fread($mem, 5)); 51 | if ($base_info['order'] !== 1) { 52 | throw new Exception('Only NDR (little endian) SKB format is supported at the moment'); 53 | } 54 | 55 | if ($base_info['z']) { 56 | $this->dimension++; 57 | $this->z = TRUE; 58 | } 59 | if ($base_info['m']) { 60 | $this->dimension++; 61 | $this->m = TRUE; 62 | } 63 | 64 | // If there is SRID information, ignore it - use EWKB Adapter to get SRID support 65 | if ($base_info['s']) { 66 | fread($mem, 4); 67 | } 68 | 69 | switch ($base_info['type']) { 70 | case 1: 71 | return $this->getPoint($mem); 72 | case 2: 73 | return $this->getLinstring($mem); 74 | case 3: 75 | return $this->getPolygon($mem); 76 | case 4: 77 | return $this->getMulti($mem,'point'); 78 | case 5: 79 | return $this->getMulti($mem,'line'); 80 | case 6: 81 | return $this->getMulti($mem,'polygon'); 82 | case 7: 83 | return $this->getMulti($mem,'geometry'); 84 | } 85 | } 86 | 87 | function getPoint(&$mem) { 88 | $point_coords = unpack("d*", fread($mem,$this->dimension*8)); 89 | if (!empty($point_coords)) { 90 | return new Point($point_coords[1],$point_coords[2]); 91 | } 92 | else { 93 | return new Point(); // EMPTY point 94 | } 95 | } 96 | 97 | function getLinstring(&$mem) { 98 | // Get the number of points expected in this string out of the first 4 bytes 99 | $line_length = unpack('L',fread($mem,4)); 100 | 101 | // Return an empty linestring if there is no line-length 102 | if (!$line_length[1]) return new LineString(); 103 | 104 | // Read the nubmer of points x2 (each point is two coords) into decimal-floats 105 | $line_coords = unpack('d*', fread($mem,$line_length[1]*$this->dimension*8)); 106 | 107 | // We have our coords, build up the linestring 108 | $components = array(); 109 | $i = 1; 110 | $num_coords = count($line_coords); 111 | while ($i <= $num_coords) { 112 | $components[] = new Point($line_coords[$i],$line_coords[$i+1]); 113 | $i += 2; 114 | } 115 | return new LineString($components); 116 | } 117 | 118 | function getPolygon(&$mem) { 119 | // Get the number of linestring expected in this poly out of the first 4 bytes 120 | $poly_length = unpack('L',fread($mem,4)); 121 | 122 | $components = array(); 123 | $i = 1; 124 | while ($i <= $poly_length[1]) { 125 | $components[] = $this->getLinstring($mem); 126 | $i++; 127 | } 128 | return new Polygon($components); 129 | } 130 | 131 | function getMulti(&$mem, $type) { 132 | // Get the number of items expected in this multi out of the first 4 bytes 133 | $multi_length = unpack('L',fread($mem,4)); 134 | 135 | $components = array(); 136 | $i = 1; 137 | while ($i <= $multi_length[1]) { 138 | $components[] = $this->getGeometry($mem); 139 | $i++; 140 | } 141 | switch ($type) { 142 | case 'point': 143 | return new MultiPoint($components); 144 | case 'line': 145 | return new MultiLineString($components); 146 | case 'polygon': 147 | return new MultiPolygon($components); 148 | case 'geometry': 149 | return new GeometryCollection($components); 150 | } 151 | } 152 | 153 | /** 154 | * Serialize geometries into WKB string. 155 | * 156 | * @param Geometry $geometry 157 | * 158 | * @return string The WKB string representation of the input geometries 159 | */ 160 | public function write(Geometry $geometry, $write_as_hex = FALSE) { 161 | // We always write into NDR (little endian) 162 | $wkb = pack('c',1); 163 | 164 | switch ($geometry->getGeomType()) { 165 | case 'Point'; 166 | $wkb .= pack('L',1); 167 | $wkb .= $this->writePoint($geometry); 168 | break; 169 | case 'LineString'; 170 | $wkb .= pack('L',2); 171 | $wkb .= $this->writeLineString($geometry); 172 | break; 173 | case 'Polygon'; 174 | $wkb .= pack('L',3); 175 | $wkb .= $this->writePolygon($geometry); 176 | break; 177 | case 'MultiPoint'; 178 | $wkb .= pack('L',4); 179 | $wkb .= $this->writeMulti($geometry); 180 | break; 181 | case 'MultiLineString'; 182 | $wkb .= pack('L',5); 183 | $wkb .= $this->writeMulti($geometry); 184 | break; 185 | case 'MultiPolygon'; 186 | $wkb .= pack('L',6); 187 | $wkb .= $this->writeMulti($geometry); 188 | break; 189 | case 'GeometryCollection'; 190 | $wkb .= pack('L',7); 191 | $wkb .= $this->writeMulti($geometry); 192 | break; 193 | } 194 | 195 | if ($write_as_hex) { 196 | $unpacked = unpack('H*',$wkb); 197 | return $unpacked[1]; 198 | } 199 | else { 200 | return $wkb; 201 | } 202 | } 203 | 204 | function writePoint($point) { 205 | // Set the coords 206 | if (!$point->isEmpty()) { 207 | $wkb = pack('dd',$point->x(), $point->y()); 208 | return $wkb; 209 | } else { 210 | return ''; 211 | } 212 | } 213 | 214 | function writeLineString($line) { 215 | // Set the number of points in this line 216 | $wkb = pack('L',$line->numPoints()); 217 | 218 | // Set the coords 219 | foreach ($line->getComponents() as $point) { 220 | $wkb .= pack('dd',$point->x(), $point->y()); 221 | } 222 | 223 | return $wkb; 224 | } 225 | 226 | function writePolygon($poly) { 227 | // Set the number of lines in this poly 228 | $wkb = pack('L',$poly->numGeometries()); 229 | 230 | // Write the lines 231 | foreach ($poly->getComponents() as $line) { 232 | $wkb .= $this->writeLineString($line); 233 | } 234 | 235 | return $wkb; 236 | } 237 | 238 | function writeMulti($geometry) { 239 | // Set the number of components 240 | $wkb = pack('L',$geometry->numGeometries()); 241 | 242 | // Write the components 243 | foreach ($geometry->getComponents() as $component) { 244 | $wkb .= $this->write($component); 245 | } 246 | 247 | return $wkb; 248 | } 249 | 250 | } 251 | -------------------------------------------------------------------------------- /lib/adapters/WKT.class.php: -------------------------------------------------------------------------------- 1 | read($wkt)); 34 | $geom->setSRID($srid); 35 | return $geom; 36 | } 37 | else { 38 | return geoPHP::geosToGeometry($reader->read($wkt)); 39 | } 40 | } 41 | $wkt = str_replace(', ', ',', $wkt); 42 | 43 | // For each geometry type, check to see if we have a match at the 44 | // beginning of the string. If we do, then parse using that type 45 | foreach (geoPHP::geometryList() as $geom_type) { 46 | $wkt_geom = strtoupper($geom_type); 47 | if (strtoupper(substr($wkt, 0, strlen($wkt_geom))) == $wkt_geom) { 48 | $data_string = $this->getDataString($wkt); 49 | $method = 'parse'.$geom_type; 50 | 51 | if ($srid) { 52 | $geom = $this->$method($data_string); 53 | $geom->setSRID($srid); 54 | return $geom; 55 | } 56 | else { 57 | return $this->$method($data_string); 58 | } 59 | 60 | } 61 | } 62 | } 63 | 64 | private function parsePoint($data_string) { 65 | $data_string = $this->trimParens($data_string); 66 | 67 | // If it's marked as empty, then return an empty point 68 | if ($data_string == 'EMPTY') return new Point(); 69 | 70 | $parts = explode(' ',$data_string); 71 | return new Point($parts[0], $parts[1]); 72 | } 73 | 74 | private function parseLineString($data_string) { 75 | $data_string = $this->trimParens($data_string); 76 | 77 | // If it's marked as empty, then return an empty line 78 | if ($data_string == 'EMPTY') return new LineString(); 79 | 80 | $parts = explode(',',$data_string); 81 | $points = array(); 82 | foreach ($parts as $part) { 83 | $points[] = $this->parsePoint($part); 84 | } 85 | return new LineString($points); 86 | } 87 | 88 | private function parsePolygon($data_string) { 89 | $data_string = $this->trimParens($data_string); 90 | 91 | // If it's marked as empty, then return an empty polygon 92 | if ($data_string == 'EMPTY') return new Polygon(); 93 | 94 | $parts = explode('),(',$data_string); 95 | $lines = array(); 96 | foreach ($parts as $part) { 97 | if (!$this->beginsWith($part,'(')) $part = '(' . $part; 98 | if (!$this->endsWith($part,')')) $part = $part . ')'; 99 | $lines[] = $this->parseLineString($part); 100 | } 101 | return new Polygon($lines); 102 | } 103 | 104 | private function parseMultiPoint($data_string) { 105 | $data_string = $this->trimParens($data_string); 106 | 107 | // If it's marked as empty, then return an empty MutiPoint 108 | if ($data_string == 'EMPTY') return new MultiPoint(); 109 | 110 | $parts = explode(',',$data_string); 111 | $points = array(); 112 | foreach ($parts as $part) { 113 | $points[] = $this->parsePoint($part); 114 | } 115 | return new MultiPoint($points); 116 | } 117 | 118 | private function parseMultiLineString($data_string) { 119 | $data_string = $this->trimParens($data_string); 120 | 121 | // If it's marked as empty, then return an empty multi-linestring 122 | if ($data_string == 'EMPTY') return new MultiLineString(); 123 | 124 | $parts = explode('),(',$data_string); 125 | $lines = array(); 126 | foreach ($parts as $part) { 127 | // Repair the string if the explode broke it 128 | if (!$this->beginsWith($part,'(')) $part = '(' . $part; 129 | if (!$this->endsWith($part,')')) $part = $part . ')'; 130 | $lines[] = $this->parseLineString($part); 131 | } 132 | return new MultiLineString($lines); 133 | } 134 | 135 | private function parseMultiPolygon($data_string) { 136 | $data_string = $this->trimParens($data_string); 137 | 138 | // If it's marked as empty, then return an empty multi-polygon 139 | if ($data_string == 'EMPTY') return new MultiPolygon(); 140 | 141 | $parts = explode(')),((',$data_string); 142 | $polys = array(); 143 | foreach ($parts as $part) { 144 | // Repair the string if the explode broke it 145 | if (!$this->beginsWith($part,'((')) $part = '((' . $part; 146 | if (!$this->endsWith($part,'))')) $part = $part . '))'; 147 | $polys[] = $this->parsePolygon($part); 148 | } 149 | return new MultiPolygon($polys); 150 | } 151 | 152 | private function parseGeometryCollection($data_string) { 153 | $data_string = $this->trimParens($data_string); 154 | 155 | // If it's marked as empty, then return an empty geom-collection 156 | if ($data_string == 'EMPTY') return new GeometryCollection(); 157 | 158 | $geometries = array(); 159 | $matches = array(); 160 | $str = preg_replace('/,\s*([A-Za-z])/', '|$1', $data_string); 161 | $components = explode('|', trim($str)); 162 | 163 | foreach ($components as $component) { 164 | $geometries[] = $this->read($component); 165 | } 166 | return new GeometryCollection($geometries); 167 | } 168 | 169 | protected function getDataString($wkt) { 170 | $first_paren = strpos($wkt, '('); 171 | 172 | if ($first_paren !== FALSE) { 173 | return substr($wkt, $first_paren); 174 | } elseif (strstr($wkt,'EMPTY')) { 175 | return 'EMPTY'; 176 | } else 177 | return FALSE; 178 | } 179 | 180 | /** 181 | * Trim the parenthesis and spaces 182 | */ 183 | protected function trimParens($str) { 184 | $str = trim($str); 185 | 186 | // We want to only strip off one set of parenthesis 187 | if ($this->beginsWith($str, '(')) { 188 | return substr($str,1,-1); 189 | } 190 | else return $str; 191 | } 192 | 193 | protected function beginsWith($str, $char) { 194 | if (substr($str,0,strlen($char)) == $char) return TRUE; 195 | else return FALSE; 196 | } 197 | 198 | protected function endsWith($str, $char) { 199 | if (substr($str,(0 - strlen($char))) == $char) return TRUE; 200 | else return FALSE; 201 | } 202 | 203 | /** 204 | * Serialize geometries into a WKT string. 205 | * 206 | * @param Geometry $geometry 207 | * 208 | * @return string The WKT string representation of the input geometries 209 | */ 210 | public function write(Geometry $geometry) { 211 | // If geos is installed, then we take a shortcut and let it write the WKT 212 | if (geoPHP::geosInstalled()) { 213 | $writer = new GEOSWKTWriter(); 214 | $writer->setTrim(TRUE); 215 | return $writer->write($geometry->geos()); 216 | } 217 | 218 | if ($geometry->isEmpty()) { 219 | return strtoupper($geometry->geometryType()).' EMPTY'; 220 | } 221 | else if ($data = $this->extractData($geometry)) { 222 | return strtoupper($geometry->geometryType()).' ('.$data.')'; 223 | } 224 | } 225 | 226 | /** 227 | * Extract geometry to a WKT string 228 | * 229 | * @param Geometry $geometry A Geometry object 230 | * 231 | * @return string 232 | */ 233 | public function extractData($geometry) { 234 | $parts = array(); 235 | switch ($geometry->geometryType()) { 236 | case 'Point': 237 | return $geometry->getX().' '.$geometry->getY(); 238 | case 'LineString': 239 | foreach ($geometry->getComponents() as $component) { 240 | $parts[] = $this->extractData($component); 241 | } 242 | return implode(', ', $parts); 243 | case 'Polygon': 244 | case 'MultiPoint': 245 | case 'MultiLineString': 246 | case 'MultiPolygon': 247 | foreach ($geometry->getComponents() as $component) { 248 | $parts[] = '('.$this->extractData($component).')'; 249 | } 250 | return implode(', ', $parts); 251 | case 'GeometryCollection': 252 | foreach ($geometry->getComponents() as $component) { 253 | $parts[] = strtoupper($component->geometryType()).' ('.$this->extractData($component).')'; 254 | } 255 | return implode(', ', $parts); 256 | } 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /lib/geometry/Collection.class.php: -------------------------------------------------------------------------------- 1 | components[] = $component; 27 | } 28 | else { 29 | throw new Exception("Cannot create a collection with non-geometries"); 30 | } 31 | } 32 | } 33 | 34 | /** 35 | * Returns Collection component geometries 36 | * 37 | * @return array 38 | */ 39 | public function getComponents() { 40 | return $this->components; 41 | } 42 | 43 | /* 44 | * Author : Adam Cherti 45 | * 46 | * inverts x and y coordinates 47 | * Useful for old data still using lng lat 48 | * 49 | * @return void 50 | * 51 | * */ 52 | public function invertxy() 53 | { 54 | for($i=0;$icomponents);$i++) 55 | { 56 | if( method_exists($this->components[$i], 'invertxy' ) ) 57 | $this->components[$i]->invertxy(); 58 | } 59 | } 60 | 61 | public function centroid() { 62 | if ($this->isEmpty()) return NULL; 63 | 64 | if ($this->geos()) { 65 | $geos_centroid = $this->geos()->centroid(); 66 | if ($geos_centroid->typeName() == 'Point') { 67 | return geoPHP::geosToGeometry($this->geos()->centroid()); 68 | } 69 | } 70 | 71 | // As a rough estimate, we say that the centroid of a colletion is the centroid of it's envelope 72 | // @@TODO: Make this the centroid of the convexHull 73 | // Note: Outside of polygons, geometryCollections and the trivial case of points, there is no standard on what a "centroid" is 74 | $centroid = $this->envelope()->centroid(); 75 | 76 | return $centroid; 77 | } 78 | 79 | public function getBBox() { 80 | if ($this->isEmpty()) return NULL; 81 | 82 | if ($this->geos()) { 83 | $envelope = $this->geos()->envelope(); 84 | if ($envelope->typeName() == 'Point') { 85 | return geoPHP::geosToGeometry($envelope)->getBBOX(); 86 | } 87 | 88 | $geos_ring = $envelope->exteriorRing(); 89 | return array( 90 | 'maxy' => $geos_ring->pointN(3)->getY(), 91 | 'miny' => $geos_ring->pointN(1)->getY(), 92 | 'maxx' => $geos_ring->pointN(1)->getX(), 93 | 'minx' => $geos_ring->pointN(3)->getX(), 94 | ); 95 | } 96 | 97 | // Go through each component and get the max and min x and y 98 | $i = 0; 99 | foreach ($this->components as $component) { 100 | $component_bbox = $component->getBBox(); 101 | 102 | // On the first run through, set the bbox to the component bbox 103 | if ($i == 0) { 104 | $maxx = $component_bbox['maxx']; 105 | $maxy = $component_bbox['maxy']; 106 | $minx = $component_bbox['minx']; 107 | $miny = $component_bbox['miny']; 108 | } 109 | 110 | // Do a check and replace on each boundary, slowly growing the bbox 111 | $maxx = $component_bbox['maxx'] > $maxx ? $component_bbox['maxx'] : $maxx; 112 | $maxy = $component_bbox['maxy'] > $maxy ? $component_bbox['maxy'] : $maxy; 113 | $minx = $component_bbox['minx'] < $minx ? $component_bbox['minx'] : $minx; 114 | $miny = $component_bbox['miny'] < $miny ? $component_bbox['miny'] : $miny; 115 | $i++; 116 | } 117 | 118 | return array( 119 | 'maxy' => $maxy, 120 | 'miny' => $miny, 121 | 'maxx' => $maxx, 122 | 'minx' => $minx, 123 | ); 124 | } 125 | 126 | public function asArray() { 127 | $array = array(); 128 | foreach ($this->components as $component) { 129 | $array[] = $component->asArray(); 130 | } 131 | return $array; 132 | } 133 | 134 | public function area() { 135 | if ($this->geos()) { 136 | return $this->geos()->area(); 137 | } 138 | 139 | $area = 0; 140 | foreach ($this->components as $component) { 141 | $area += $component->area(); 142 | } 143 | return $area; 144 | } 145 | 146 | // By default, the boundary of a collection is the boundary of it's components 147 | public function boundary() { 148 | if ($this->isEmpty()) return new LineString(); 149 | 150 | if ($this->geos()) { 151 | return $this->geos()->boundary(); 152 | } 153 | 154 | $components_boundaries = array(); 155 | foreach ($this->components as $component) { 156 | $components_boundaries[] = $component->boundary(); 157 | } 158 | return geoPHP::geometryReduce($components_boundaries); 159 | } 160 | 161 | public function numGeometries() { 162 | return count($this->components); 163 | } 164 | 165 | // Note that the standard is 1 based indexing 166 | public function geometryN($n) { 167 | $n = intval($n); 168 | if (array_key_exists($n-1, $this->components)) { 169 | return $this->components[$n-1]; 170 | } 171 | else { 172 | return NULL; 173 | } 174 | } 175 | 176 | public function length() { 177 | $length = 0; 178 | foreach ($this->components as $delta => $component) { 179 | $length += $component->length(); 180 | } 181 | return $length; 182 | } 183 | 184 | public function greatCircleLength($radius = 6378137) { 185 | $length = 0; 186 | foreach ($this->components as $component) { 187 | $length += $component->greatCircleLength($radius); 188 | } 189 | return $length; 190 | } 191 | 192 | public function haversineLength() { 193 | $length = 0; 194 | foreach ($this->components as $component) { 195 | $length += $component->haversineLength(); 196 | } 197 | return $length; 198 | } 199 | 200 | public function dimension() { 201 | $dimension = 0; 202 | foreach ($this->components as $component) { 203 | if ($component->dimension() > $dimension) { 204 | $dimension = $component->dimension(); 205 | } 206 | } 207 | return $dimension; 208 | } 209 | 210 | // A collection is empty if it has no components OR all it's components are empty 211 | public function isEmpty() { 212 | if (!count($this->components)) { 213 | return TRUE; 214 | } 215 | else { 216 | foreach ($this->components as $component) { 217 | if (!$component->isEmpty()) return FALSE; 218 | } 219 | return TRUE; 220 | } 221 | } 222 | 223 | public function numPoints() { 224 | $num = 0; 225 | foreach ($this->components as $component) { 226 | $num += $component->numPoints(); 227 | } 228 | return $num; 229 | } 230 | 231 | public function getPoints() { 232 | $points = array(); 233 | foreach ($this->components as $component) { 234 | $points = array_merge($points, $component->getPoints()); 235 | } 236 | return $points; 237 | } 238 | 239 | public function equals($geometry) { 240 | if ($this->geos()) { 241 | return $this->geos()->equals($geometry->geos()); 242 | } 243 | 244 | // To test for equality we check to make sure that there is a matching point 245 | // in the other geometry for every point in this geometry. 246 | // This is slightly more strict than the standard, which 247 | // uses Within(A,B) = true and Within(B,A) = true 248 | // @@TODO: Eventually we could fix this by using some sort of simplification 249 | // method that strips redundant vertices (that are all in a row) 250 | 251 | $this_points = $this->getPoints(); 252 | $other_points = $geometry->getPoints(); 253 | 254 | // First do a check to make sure they have the same number of vertices 255 | if (count($this_points) != count($other_points)) { 256 | return FALSE; 257 | } 258 | 259 | foreach ($this_points as $point) { 260 | $found_match = FALSE; 261 | foreach ($other_points as $key => $test_point) { 262 | if ($point->equals($test_point)) { 263 | $found_match = TRUE; 264 | unset($other_points[$key]); 265 | break; 266 | } 267 | } 268 | if (!$found_match) { 269 | return FALSE; 270 | } 271 | } 272 | 273 | // All points match, return TRUE 274 | return TRUE; 275 | } 276 | 277 | public function isSimple() { 278 | if ($this->geos()) { 279 | return $this->geos()->isSimple(); 280 | } 281 | 282 | // A collection is simple if all it's components are simple 283 | foreach ($this->components as $component) { 284 | if (!$component->isSimple()) return FALSE; 285 | } 286 | 287 | return TRUE; 288 | } 289 | 290 | public function explode() { 291 | $parts = array(); 292 | foreach ($this->components as $component) { 293 | $parts = array_merge($parts, $component->explode()); 294 | } 295 | return $parts; 296 | } 297 | 298 | // Not valid for this geometry type 299 | // -------------------------------- 300 | public function x() { return NULL; } 301 | public function y() { return NULL; } 302 | public function startPoint() { return NULL; } 303 | public function endPoint() { return NULL; } 304 | public function isRing() { return NULL; } 305 | public function isClosed() { return NULL; } 306 | public function pointN($n) { return NULL; } 307 | public function exteriorRing() { return NULL; } 308 | public function numInteriorRings() { return NULL; } 309 | public function interiorRingN($n) { return NULL; } 310 | public function pointOnSurface() { return NULL; } 311 | } 312 | 313 | -------------------------------------------------------------------------------- /lib/geometry/Geometry.class.php: -------------------------------------------------------------------------------- 1 | srid; 50 | } 51 | 52 | public function setSRID($srid) { 53 | if ($this->geos()) { 54 | $this->geos()->setSRID($srid); 55 | } 56 | $this->srid = $srid; 57 | } 58 | 59 | public function envelope() { 60 | if ($this->isEmpty()) return new Polygon(); 61 | 62 | if ($this->geos()) { 63 | return geoPHP::geosToGeometry($this->geos()->envelope()); 64 | } 65 | 66 | $bbox = $this->getBBox(); 67 | $points = array ( 68 | new Point($bbox['maxx'],$bbox['miny']), 69 | new Point($bbox['maxx'],$bbox['maxy']), 70 | new Point($bbox['minx'],$bbox['maxy']), 71 | new Point($bbox['minx'],$bbox['miny']), 72 | new Point($bbox['maxx'],$bbox['miny']), 73 | ); 74 | 75 | $outer_boundary = new LineString($points); 76 | return new Polygon(array($outer_boundary)); 77 | } 78 | 79 | public function geometryType() { 80 | return $this->geom_type; 81 | } 82 | 83 | // Public: Non-Standard -- Common to all geometries 84 | // ------------------------------------------------ 85 | 86 | // $this->out($format, $other_args); 87 | public function out() { 88 | $args = func_get_args(); 89 | 90 | $format = array_shift($args); 91 | $type_map = geoPHP::getAdapterMap(); 92 | $processor_type = $type_map[$format]; 93 | $processor = new $processor_type(); 94 | 95 | array_unshift($args, $this); 96 | $result = call_user_func_array(array($processor, 'write'), $args); 97 | 98 | return $result; 99 | } 100 | 101 | 102 | // Public: Aliases 103 | // --------------- 104 | public function getCentroid() { 105 | return $this->centroid(); 106 | } 107 | 108 | public function getArea() { 109 | return $this->area(); 110 | } 111 | 112 | public function getX() { 113 | return $this->x(); 114 | } 115 | 116 | public function getY() { 117 | return $this->y(); 118 | } 119 | 120 | public function getGeos() { 121 | return $this->geos(); 122 | } 123 | 124 | public function getGeomType() { 125 | return $this->geometryType(); 126 | } 127 | 128 | public function getSRID() { 129 | return $this->SRID(); 130 | } 131 | 132 | public function asText() { 133 | return $this->out('wkt'); 134 | } 135 | 136 | public function asBinary() { 137 | return $this->out('wkb'); 138 | } 139 | 140 | // Public: GEOS Only Functions 141 | // --------------------------- 142 | public function geos() { 143 | // If it's already been set, just return it 144 | if ($this->geos && geoPHP::geosInstalled()) { 145 | return $this->geos; 146 | } 147 | // It hasn't been set yet, generate it 148 | if (geoPHP::geosInstalled()) { 149 | $reader = new GEOSWKBReader(); 150 | $this->geos = $reader->readHEX($this->out('wkb',TRUE)); 151 | } 152 | else { 153 | $this->geos = FALSE; 154 | } 155 | return $this->geos; 156 | } 157 | 158 | public function setGeos($geos) { 159 | $this->geos = $geos; 160 | } 161 | 162 | public function pointOnSurface() { 163 | if ($this->geos()) { 164 | return geoPHP::geosToGeometry($this->geos()->pointOnSurface()); 165 | } 166 | } 167 | 168 | public function equalsExact(Geometry $geometry) { 169 | if ($this->geos()) { 170 | return $this->geos()->equalsExact($geometry->geos()); 171 | } 172 | } 173 | 174 | public function relate(Geometry $geometry, $pattern = NULL) { 175 | if ($this->geos()) { 176 | if ($pattern) { 177 | return $this->geos()->relate($geometry->geos(), $pattern); 178 | } 179 | else { 180 | return $this->geos()->relate($geometry->geos()); 181 | } 182 | } 183 | } 184 | 185 | public function checkValidity() { 186 | if ($this->geos()) { 187 | return $this->geos()->checkValidity(); 188 | } 189 | } 190 | 191 | public function buffer($distance) { 192 | if ($this->geos()) { 193 | return geoPHP::geosToGeometry($this->geos()->buffer($distance)); 194 | } 195 | } 196 | 197 | public function intersection(Geometry $geometry) { 198 | if ($this->geos()) { 199 | return geoPHP::geosToGeometry($this->geos()->intersection($geometry->geos())); 200 | } 201 | } 202 | 203 | public function convexHull() { 204 | if ($this->geos()) { 205 | return geoPHP::geosToGeometry($this->geos()->convexHull()); 206 | } 207 | } 208 | 209 | public function difference(Geometry $geometry) { 210 | if ($this->geos()) { 211 | return geoPHP::geosToGeometry($this->geos()->difference($geometry->geos())); 212 | } 213 | } 214 | 215 | public function symDifference(Geometry $geometry) { 216 | if ($this->geos()) { 217 | return geoPHP::geosToGeometry($this->geos()->symDifference($geometry->geos())); 218 | } 219 | } 220 | 221 | // Can pass in a geometry or an array of geometries 222 | public function union(Geometry $geometry) { 223 | if ($this->geos()) { 224 | if (is_array($geometry)) { 225 | $geom = $this->geos(); 226 | foreach ($geometry as $item) { 227 | $geom = $geom->union($item->geos()); 228 | } 229 | return geoPHP::geosToGeometry($geom); 230 | } 231 | else { 232 | return geoPHP::geosToGeometry($this->geos()->union($geometry->geos())); 233 | } 234 | } 235 | } 236 | 237 | public function simplify($tolerance, $preserveTopology = FALSE) { 238 | if ($this->geos()) { 239 | return geoPHP::geosToGeometry($this->geos()->simplify($tolerance, $preserveTopology)); 240 | } 241 | } 242 | 243 | public function disjoint(Geometry $geometry) { 244 | if ($this->geos()) { 245 | return $this->geos()->disjoint($geometry->geos()); 246 | } 247 | } 248 | 249 | public function touches(Geometry $geometry) { 250 | if ($this->geos()) { 251 | return $this->geos()->touches($geometry->geos()); 252 | } 253 | } 254 | 255 | public function intersects(Geometry $geometry) { 256 | if ($this->geos()) { 257 | return $this->geos()->intersects($geometry->geos()); 258 | } 259 | } 260 | 261 | public function crosses(Geometry $geometry) { 262 | if ($this->geos()) { 263 | return $this->geos()->crosses($geometry->geos()); 264 | } 265 | } 266 | 267 | public function within(Geometry $geometry) { 268 | if ($this->geos()) { 269 | return $this->geos()->within($geometry->geos()); 270 | } 271 | } 272 | 273 | public function contains(Geometry $geometry) { 274 | if ($this->geos()) { 275 | return $this->geos()->contains($geometry->geos()); 276 | } 277 | } 278 | 279 | public function overlaps(Geometry $geometry) { 280 | if ($this->geos()) { 281 | return $this->geos()->overlaps($geometry->geos()); 282 | } 283 | } 284 | 285 | public function covers(Geometry $geometry) { 286 | if ($this->geos()) { 287 | return $this->geos()->covers($geometry->geos()); 288 | } 289 | } 290 | 291 | public function coveredBy(Geometry $geometry) { 292 | if ($this->geos()) { 293 | return $this->geos()->coveredBy($geometry->geos()); 294 | } 295 | } 296 | 297 | public function distance(Geometry $geometry) { 298 | if ($this->geos()) { 299 | return $this->geos()->distance($geometry->geos()); 300 | } 301 | } 302 | 303 | public function hausdorffDistance(Geometry $geometry) { 304 | if ($this->geos()) { 305 | return $this->geos()->hausdorffDistance($geometry->geos()); 306 | } 307 | } 308 | 309 | public function project(Geometry $point, $normalized = NULL) { 310 | if ($this->geos()) { 311 | return $this->geos()->project($point->geos(), $normalized); 312 | } 313 | } 314 | 315 | // Public - Placeholders 316 | // --------------------- 317 | public function hasZ() { 318 | // geoPHP does not support Z values at the moment 319 | return FALSE; 320 | } 321 | 322 | public function is3D() { 323 | // geoPHP does not support 3D geometries at the moment 324 | return FALSE; 325 | } 326 | 327 | public function isMeasured() { 328 | // geoPHP does not yet support M values 329 | return FALSE; 330 | } 331 | 332 | public function coordinateDimension() { 333 | // geoPHP only supports 2-dimensional space 334 | return 2; 335 | } 336 | 337 | public function z() { 338 | // geoPHP only supports 2-dimensional space 339 | return NULL; 340 | } 341 | 342 | public function m() { 343 | // geoPHP only supports 2-dimensional space 344 | return NULL; 345 | } 346 | 347 | } 348 | -------------------------------------------------------------------------------- /lib/geometry/GeometryCollection.class.php: -------------------------------------------------------------------------------- 1 | components as $component) { 17 | $array[] = array( 18 | 'type' => $component->geometryType(), 19 | 'components' => $component->asArray(), 20 | ); 21 | } 22 | return $array; 23 | } 24 | 25 | // Not valid for this geomettry 26 | public function boundary() { return NULL; } 27 | public function isSimple() { return NULL; } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /lib/geometry/LineString.class.php: -------------------------------------------------------------------------------- 1 | pointN(1); 32 | } 33 | 34 | public function endPoint() { 35 | $last_n = $this->numPoints(); 36 | return $this->pointN($last_n); 37 | } 38 | 39 | public function isClosed() { 40 | return ($this->startPoint()->equals($this->endPoint())); 41 | } 42 | 43 | public function isRing() { 44 | return ($this->isClosed() && $this->isSimple()); 45 | } 46 | 47 | public function numPoints() { 48 | return $this->numGeometries(); 49 | } 50 | 51 | public function pointN($n) { 52 | return $this->geometryN($n); 53 | } 54 | 55 | public function dimension() { 56 | if ($this->isEmpty()) return 0; 57 | return 1; 58 | } 59 | 60 | public function area() { 61 | return 0; 62 | } 63 | 64 | public function length() { 65 | if ($this->geos()) { 66 | return $this->geos()->length(); 67 | } 68 | $length = 0; 69 | foreach ($this->getPoints() as $delta => $point) { 70 | $previous_point = $this->geometryN($delta); 71 | if ($previous_point) { 72 | $length += sqrt(pow(($previous_point->getX() - $point->getX()), 2) + pow(($previous_point->getY()- $point->getY()), 2)); 73 | } 74 | } 75 | return $length; 76 | } 77 | 78 | public function greatCircleLength($radius = 6378137) { 79 | $length = 0; 80 | $points = $this->getPoints(); 81 | for($i=0; $i<$this->numPoints()-1; $i++) { 82 | $point = $points[$i]; 83 | $next_point = $points[$i+1]; 84 | if (!is_object($next_point)) {continue;} 85 | // Great circle method 86 | $lat1 = deg2rad($point->getY()); 87 | $lat2 = deg2rad($next_point->getY()); 88 | $lon1 = deg2rad($point->getX()); 89 | $lon2 = deg2rad($next_point->getX()); 90 | $dlon = $lon2 - $lon1; 91 | $length += 92 | $radius * 93 | atan2( 94 | sqrt( 95 | pow(cos($lat2) * sin($dlon), 2) + 96 | pow(cos($lat1) * sin($lat2) - sin($lat1) * cos($lat2) * cos($dlon), 2) 97 | ) 98 | , 99 | sin($lat1) * sin($lat2) + 100 | cos($lat1) * cos($lat2) * cos($dlon) 101 | ); 102 | } 103 | // Returns length in meters. 104 | return $length; 105 | } 106 | 107 | public function haversineLength() { 108 | $degrees = 0; 109 | $points = $this->getPoints(); 110 | for($i=0; $i<$this->numPoints()-1; $i++) { 111 | $point = $points[$i]; 112 | $next_point = $points[$i+1]; 113 | if (!is_object($next_point)) {continue;} 114 | $degree = rad2deg( 115 | acos( 116 | sin(deg2rad($point->getY())) * sin(deg2rad($next_point->getY())) + 117 | cos(deg2rad($point->getY())) * cos(deg2rad($next_point->getY())) * 118 | cos(deg2rad(abs($point->getX() - $next_point->getX()))) 119 | ) 120 | ); 121 | $degrees += $degree; 122 | } 123 | // Returns degrees 124 | return $degrees; 125 | } 126 | 127 | public function explode() { 128 | $parts = array(); 129 | $points = $this->getPoints(); 130 | 131 | foreach ($points as $i => $point) { 132 | if (isset($points[$i+1])) { 133 | $parts[] = new LineString(array($point, $points[$i+1])); 134 | } 135 | } 136 | return $parts; 137 | } 138 | 139 | public function isSimple() { 140 | if ($this->geos()) { 141 | return $this->geos()->isSimple(); 142 | } 143 | 144 | $segments = $this->explode(); 145 | 146 | foreach ($segments as $i => $segment) { 147 | foreach ($segments as $j => $check_segment) { 148 | if ($i != $j) { 149 | if ($segment->lineSegmentIntersect($check_segment)) { 150 | return FALSE; 151 | } 152 | } 153 | } 154 | } 155 | return TRUE; 156 | } 157 | 158 | // Utility function to check if any line sigments intersect 159 | // Derived from http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect 160 | public function lineSegmentIntersect($segment) { 161 | $p0_x = $this->startPoint()->x(); 162 | $p0_y = $this->startPoint()->y(); 163 | $p1_x = $this->endPoint()->x(); 164 | $p1_y = $this->endPoint()->y(); 165 | $p2_x = $segment->startPoint()->x(); 166 | $p2_y = $segment->startPoint()->y(); 167 | $p3_x = $segment->endPoint()->x(); 168 | $p3_y = $segment->endPoint()->y(); 169 | 170 | $s1_x = $p1_x - $p0_x; $s1_y = $p1_y - $p0_y; 171 | $s2_x = $p3_x - $p2_x; $s2_y = $p3_y - $p2_y; 172 | 173 | $fps = (-$s2_x * $s1_y) + ($s1_x * $s2_y); 174 | $fpt = (-$s2_x * $s1_y) + ($s1_x * $s2_y); 175 | 176 | if ($fps == 0 || $fpt == 0) { 177 | return FALSE; 178 | } 179 | 180 | $s = (-$s1_y * ($p0_x - $p2_x) + $s1_x * ($p0_y - $p2_y)) / $fps; 181 | $t = ( $s2_x * ($p0_y - $p2_y) - $s2_y * ($p0_x - $p2_x)) / $fpt; 182 | 183 | if ($s > 0 && $s < 1 && $t > 0 && $t < 1) { 184 | // Collision detected 185 | return TRUE; 186 | } 187 | return FALSE; 188 | } 189 | } 190 | 191 | -------------------------------------------------------------------------------- /lib/geometry/MultiLineString.class.php: -------------------------------------------------------------------------------- 1 | components as $line) { 12 | if (!$line->isClosed()) { 13 | return FALSE; 14 | } 15 | } 16 | return TRUE; 17 | } 18 | 19 | } 20 | 21 | -------------------------------------------------------------------------------- /lib/geometry/MultiPoint.class.php: -------------------------------------------------------------------------------- 1 | numGeometries(); 11 | } 12 | 13 | public function isSimple() { 14 | return TRUE; 15 | } 16 | 17 | // Not valid for this geometry type 18 | // -------------------------------- 19 | public function explode() { return NULL; } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /lib/geometry/MultiPolygon.class.php: -------------------------------------------------------------------------------- 1 | coords = array(NULL, NULL); 25 | $this->dimension = 0; 26 | return; 27 | } 28 | 29 | // Basic validation on x and y 30 | if (!is_numeric($x) || !is_numeric($y)) { 31 | throw new Exception("Cannot construct Point. x and y should be numeric"); 32 | } 33 | 34 | // Check to see if this is a 3D point 35 | if ($z !== NULL) { 36 | if (!is_numeric($z)) { 37 | throw new Exception("Cannot construct Point. z should be numeric"); 38 | } 39 | $this->dimension = 3; 40 | } 41 | 42 | // Convert to floatval in case they are passed in as a string or integer etc. 43 | $x = floatval($x); 44 | $y = floatval($y); 45 | $z = floatval($z); 46 | 47 | // Add poitional elements 48 | if ($this->dimension == 2) { 49 | $this->coords = array($x, $y); 50 | } 51 | if ($this->dimension == 3) { 52 | $this->coords = array($x, $y, $z); 53 | } 54 | } 55 | 56 | /** 57 | * Get X (longitude) coordinate 58 | * 59 | * @return float The X coordinate 60 | */ 61 | public function x() { 62 | return $this->coords[0]; 63 | } 64 | 65 | /** 66 | * Returns Y (latitude) coordinate 67 | * 68 | * @return float The Y coordinate 69 | */ 70 | public function y() { 71 | return $this->coords[1]; 72 | } 73 | 74 | /** 75 | * Returns Z (altitude) coordinate 76 | * 77 | * @return float The Z coordinate or NULL is not a 3D point 78 | */ 79 | public function z() { 80 | if ($this->dimension == 3) { 81 | return $this->coords[2]; 82 | } 83 | else return NULL; 84 | } 85 | 86 | /** 87 | * Author : Adam Cherti 88 | * inverts x and y coordinates 89 | * Useful with old applications still using lng lat 90 | * 91 | * @return void 92 | * */ 93 | public function invertxy() 94 | { 95 | $x=$this->coords[0]; 96 | $this->coords[0]=$this->coords[1]; 97 | $this->coords[1]=$x; 98 | } 99 | 100 | 101 | // A point's centroid is itself 102 | public function centroid() { 103 | return $this; 104 | } 105 | 106 | public function getBBox() { 107 | return array( 108 | 'maxy' => $this->getY(), 109 | 'miny' => $this->getY(), 110 | 'maxx' => $this->getX(), 111 | 'minx' => $this->getX(), 112 | ); 113 | } 114 | 115 | public function asArray($assoc = FALSE) { 116 | return $this->coords; 117 | } 118 | 119 | public function area() { 120 | return 0; 121 | } 122 | 123 | public function length() { 124 | return 0; 125 | } 126 | 127 | public function greatCircleLength() { 128 | return 0; 129 | } 130 | 131 | public function haversineLength() { 132 | return 0; 133 | } 134 | 135 | // The boundary of a point is itself 136 | public function boundary() { 137 | return $this; 138 | } 139 | 140 | public function dimension() { 141 | return 0; 142 | } 143 | 144 | public function isEmpty() { 145 | if ($this->dimension == 0) { 146 | return TRUE; 147 | } 148 | else { 149 | return FALSE; 150 | } 151 | } 152 | 153 | public function numPoints() { 154 | return 1; 155 | } 156 | 157 | public function getPoints() { 158 | return array($this); 159 | } 160 | 161 | public function equals($geometry) { 162 | if (get_class($geometry) != 'Point') { 163 | return FALSE; 164 | } 165 | if (!$this->isEmpty() && !$geometry->isEmpty()) { 166 | /** 167 | * @see: http://php.net/manual/en/function.bccomp.php 168 | * @see: http://php.net/manual/en/language.types.float.php 169 | * @see: http://tubalmartin.github.io/spherical-geometry-php/#LatLng 170 | */ 171 | return (abs($this->x() - $geometry->x()) <= 1.0E-9 && abs($this->y() - $geometry->y()) <= 1.0E-9); 172 | } 173 | else if ($this->isEmpty() && $geometry->isEmpty()) { 174 | return TRUE; 175 | } 176 | else { 177 | return FALSE; 178 | } 179 | } 180 | 181 | public function isSimple() { 182 | return TRUE; 183 | } 184 | 185 | // Not valid for this geometry type 186 | public function numGeometries() { return NULL; } 187 | public function geometryN($n) { return NULL; } 188 | public function startPoint() { return NULL; } 189 | public function endPoint() { return NULL; } 190 | public function isRing() { return NULL; } 191 | public function isClosed() { return NULL; } 192 | public function pointN($n) { return NULL; } 193 | public function exteriorRing() { return NULL; } 194 | public function numInteriorRings() { return NULL; } 195 | public function interiorRingN($n) { return NULL; } 196 | public function pointOnSurface() { return NULL; } 197 | public function explode() { return NULL; } 198 | } 199 | 200 | -------------------------------------------------------------------------------- /lib/geometry/Polygon.class.php: -------------------------------------------------------------------------------- 1 | exteriorRing(); 14 | } 15 | 16 | public function area($exterior_only = FALSE, $signed = FALSE) { 17 | if ($this->isEmpty()) return 0; 18 | 19 | if ($this->geos() && $exterior_only == FALSE) { 20 | return $this->geos()->area(); 21 | } 22 | 23 | $exterior_ring = $this->components[0]; 24 | $pts = $exterior_ring->getComponents(); 25 | 26 | $c = count($pts); 27 | if((int)$c == '0') return NULL; 28 | $a = '0'; 29 | foreach($pts as $k => $p){ 30 | $j = ($k + 1) % $c; 31 | $a = $a + ($p->getX() * $pts[$j]->getY()) - ($p->getY() * $pts[$j]->getX()); 32 | } 33 | 34 | if ($signed) $area = ($a / 2); 35 | else $area = abs(($a / 2)); 36 | 37 | if ($exterior_only == TRUE) { 38 | return $area; 39 | } 40 | foreach ($this->components as $delta => $component) { 41 | if ($delta != 0) { 42 | $inner_poly = new Polygon(array($component)); 43 | $area -= $inner_poly->area(); 44 | } 45 | } 46 | return $area; 47 | } 48 | 49 | public function centroid() { 50 | if ($this->isEmpty()) return NULL; 51 | 52 | if ($this->geos()) { 53 | return geoPHP::geosToGeometry($this->geos()->centroid()); 54 | } 55 | 56 | $exterior_ring = $this->components[0]; 57 | $pts = $exterior_ring->getComponents(); 58 | 59 | $c = count($pts); 60 | if((int)$c == '0') return NULL; 61 | $cn = array('x' => '0', 'y' => '0'); 62 | $a = $this->area(TRUE, TRUE); 63 | 64 | // If this is a polygon with no area. Just return the first point. 65 | if ($a == 0) { 66 | return $this->exteriorRing()->pointN(1); 67 | } 68 | 69 | foreach($pts as $k => $p){ 70 | $j = ($k + 1) % $c; 71 | $P = ($p->getX() * $pts[$j]->getY()) - ($p->getY() * $pts[$j]->getX()); 72 | $cn['x'] = $cn['x'] + ($p->getX() + $pts[$j]->getX()) * $P; 73 | $cn['y'] = $cn['y'] + ($p->getY() + $pts[$j]->getY()) * $P; 74 | } 75 | 76 | $cn['x'] = $cn['x'] / ( 6 * $a); 77 | $cn['y'] = $cn['y'] / ( 6 * $a); 78 | 79 | $centroid = new Point($cn['x'], $cn['y']); 80 | return $centroid; 81 | } 82 | 83 | /** 84 | * Find the outermost point from the centroid 85 | * 86 | * @returns Point The outermost point 87 | */ 88 | public function outermostPoint() { 89 | $centroid = $this->getCentroid(); 90 | 91 | $max = array('length' => 0, 'point' => null); 92 | 93 | foreach($this->getPoints() as $point) { 94 | $lineString = new LineString(array($centroid, $point)); 95 | 96 | if($lineString->length() > $max['length']) { 97 | $max['length'] = $lineString->length(); 98 | $max['point'] = $point; 99 | } 100 | } 101 | 102 | return $max['point']; 103 | } 104 | 105 | public function exteriorRing() { 106 | if ($this->isEmpty()) return new LineString(); 107 | return $this->components[0]; 108 | } 109 | 110 | public function numInteriorRings() { 111 | if ($this->isEmpty()) return 0; 112 | return $this->numGeometries()-1; 113 | } 114 | 115 | public function interiorRingN($n) { 116 | return $this->geometryN($n+1); 117 | } 118 | 119 | public function dimension() { 120 | if ($this->isEmpty()) return 0; 121 | return 2; 122 | } 123 | 124 | public function isSimple() { 125 | if ($this->geos()) { 126 | return $this->geos()->isSimple(); 127 | } 128 | 129 | $segments = $this->explode(); 130 | 131 | foreach ($segments as $i => $segment) { 132 | foreach ($segments as $j => $check_segment) { 133 | if ($i != $j) { 134 | if ($segment->lineSegmentIntersect($check_segment)) { 135 | return FALSE; 136 | } 137 | } 138 | } 139 | } 140 | return TRUE; 141 | } 142 | 143 | /** 144 | * For a given point, determine whether it's bounded by the given polygon. 145 | * Adapted from http://www.assemblysys.com/dataServices/php_pointinpolygon.php 146 | * @see http://en.wikipedia.org/wiki/Point%5Fin%5Fpolygon 147 | * 148 | * @param Point $point 149 | * @param boolean $pointOnBoundary - whether a boundary should be considered "in" or not 150 | * @param boolean $pointOnVertex - whether a vertex should be considered "in" or not 151 | * @return boolean 152 | */ 153 | public function pointInPolygon($point, $pointOnBoundary = true, $pointOnVertex = true) { 154 | $vertices = $this->getPoints(); 155 | 156 | // Check if the point sits exactly on a vertex 157 | if ($this->pointOnVertex($point, $vertices)) { 158 | return $pointOnVertex ? TRUE : FALSE; 159 | } 160 | 161 | // Check if the point is inside the polygon or on the boundary 162 | $intersections = 0; 163 | $vertices_count = count($vertices); 164 | 165 | for ($i=1; $i < $vertices_count; $i++) { 166 | $vertex1 = $vertices[$i-1]; 167 | $vertex2 = $vertices[$i]; 168 | if ($vertex1->y() == $vertex2->y() 169 | && $vertex1->y() == $point->y() 170 | && $point->x() > min($vertex1->x(), $vertex2->x()) 171 | && $point->x() < max($vertex1->x(), $vertex2->x())) { 172 | // Check if point is on an horizontal polygon boundary 173 | return $pointOnBoundary ? TRUE : FALSE; 174 | } 175 | if ($point->y() > min($vertex1->y(), $vertex2->y()) 176 | && $point->y() <= max($vertex1->y(), $vertex2->y()) 177 | && $point->x() <= max($vertex1->x(), $vertex2->x()) 178 | && $vertex1->y() != $vertex2->y()) { 179 | $xinters = 180 | ($point->y() - $vertex1->y()) * ($vertex2->x() - $vertex1->x()) 181 | / ($vertex2->y() - $vertex1->y()) 182 | + $vertex1->x(); 183 | if ($xinters == $point->x()) { 184 | // Check if point is on the polygon boundary (other than horizontal) 185 | return $pointOnBoundary ? TRUE : FALSE; 186 | } 187 | if ($vertex1->x() == $vertex2->x() || $point->x() <= $xinters) { 188 | $intersections++; 189 | } 190 | } 191 | } 192 | // If the number of edges we passed through is even, then it's in the polygon. 193 | if ($intersections % 2 != 0) { 194 | return TRUE; 195 | } 196 | else { 197 | return FALSE; 198 | } 199 | } 200 | 201 | public function pointOnVertex($point) { 202 | foreach($this->getPoints() as $vertex) { 203 | if ($point->equals($vertex)) { 204 | return true; 205 | } 206 | } 207 | } 208 | 209 | 210 | // Not valid for this geometry type 211 | // -------------------------------- 212 | public function length() { return NULL; } 213 | 214 | } 215 | 216 | -------------------------------------------------------------------------------- /tests/input/an_empty_polygon.wkt: -------------------------------------------------------------------------------- 1 | POLYGON EMPTY -------------------------------------------------------------------------------- /tests/input/barret_spur.gpx: -------------------------------------------------------------------------------- 1 | 2 | 1374Vista Ridge TrailheadTrail Head 3 | 1777Wy'East Basin 4 | 1823Dollar Lake 5 | 2394Barrett SpurSummit 6 | 7 | Barrett Spur 1 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | Barrett Spur 2 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | -------------------------------------------------------------------------------- /tests/input/box.georss: -------------------------------------------------------------------------------- 1 | 42.943 -71.032 43.039 -69.856 2 | -------------------------------------------------------------------------------- /tests/input/cdata.kml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CDATA example 6 | 7 | CDATA Tags are useful! 9 |

Text is more readable and 10 | easier to write when you can avoid using entity 11 | references.

12 | ]]> 13 |
14 | 15 | 102.595626,14.996729 16 | 17 |
18 |
19 |
20 | -------------------------------------------------------------------------------- /tests/input/circle.georss: -------------------------------------------------------------------------------- 1 | 42.943 -71.032 500 2 | -------------------------------------------------------------------------------- /tests/input/empty_point.wkt: -------------------------------------------------------------------------------- 1 | POINT EMPTY -------------------------------------------------------------------------------- /tests/input/geometrycollection.georss: -------------------------------------------------------------------------------- 1 | 2 | 4 | Earthquakes 5 | International earthquake observation labs 6 | 7 | 2005-12-13T18:30:02Z 8 | 9 | Dr. Thaddeus Remor 10 | tremor@quakelab.edu 11 | 12 | urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6 13 | 14 | This has multiple georss entries in it 15 | 16 | urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a 17 | 2005-08-17T07:02:32Z 18 | We just had a big one. 19 | 45.256 -71.92 20 | 42.256 -69.92 21 | 45.256 -110.45 46.46 -109.48 43.84 -109.86 22 | 45.256 -110.45 46.46 -109.48 43.84 -109.86 45.256 -110.45 23 | 24 | 25 | Another Entryd 26 | Another point. 27 | 46.256 -70.92 28 | 29 | -------------------------------------------------------------------------------- /tests/input/geometrycollection.wkt: -------------------------------------------------------------------------------- 1 | GEOMETRYCOLLECTION (POINT (4 6), LINESTRING (4 6,7 10)) 2 | -------------------------------------------------------------------------------- /tests/input/line.georss: -------------------------------------------------------------------------------- 1 | 45.256 -110.45 46.46 -109.48 43.84 -109.86 2 | -------------------------------------------------------------------------------- /tests/input/linestring.wkt: -------------------------------------------------------------------------------- 1 | LINESTRING (30 10, 10 30, 40 40) 2 | -------------------------------------------------------------------------------- /tests/input/long.geohash: -------------------------------------------------------------------------------- 1 | xn76urx4epb0 2 | -------------------------------------------------------------------------------- /tests/input/multilinestring.ewkt: -------------------------------------------------------------------------------- 1 | SRID=4269;MULTILINESTRING((-71.160281 42.258729,-71.160837 42.259113,-71.161144 42.25932)) 2 | -------------------------------------------------------------------------------- /tests/input/multilinestring.wkt: -------------------------------------------------------------------------------- 1 | MULTILINESTRING ((10 10, 20 20, 10 40), (40 40, 30 30, 40 20, 30 10)) 2 | -------------------------------------------------------------------------------- /tests/input/multipolygon.wkb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phayes/geoPHP/685562416ec6d22b9b3927e02ca0ddacf84ca646/tests/input/multipolygon.wkb -------------------------------------------------------------------------------- /tests/input/multipolygon.wkt: -------------------------------------------------------------------------------- 1 | MULTIPOLYGON (((30 20, 10 40, 45 40, 30 20)), ((15 5, 40 10, 10 20, 5 10, 15 5))) 2 | -------------------------------------------------------------------------------- /tests/input/multipolygon2.wkt: -------------------------------------------------------------------------------- 1 | MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)),((20 35, 45 20, 30 5, 10 10, 10 30, 20 35),(30 20, 20 25, 20 15, 30 20))) -------------------------------------------------------------------------------- /tests/input/opposite.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Mons and its opposite 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /tests/input/path.kml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Paths 5 | Examples of paths. Note that the tessellate tag is by default 6 | set to 0. If you want to create tessellated lines, they must be authored 7 | (or edited) directly in KML. 8 | 17 | 18 | Absolute Extruded 19 | Transparent green wall with yellow outlines 20 | #yellowLineGreenPoly 21 | 22 | 1 23 | 1 24 | absolute 25 | -112.2550785337791,36.07954952145647,2357 26 | -112.2549277039738,36.08117083492122,2357 27 | -112.2552505069063,36.08260761307279,2357 28 | -112.2564540158376,36.08395660588506,2357 29 | -112.2580238976449,36.08511401044813,2357 30 | -112.2595218489022,36.08584355239394,2357 31 | -112.2608216347552,36.08612634548589,2357 32 | -112.262073428656,36.08626019085147,2357 33 | -112.2633204928495,36.08621519860091,2357 34 | -112.2644963846444,36.08627897945274,2357 35 | -112.2656969554589,36.08649599090644,2357 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /tests/input/pentagon.kml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The Pentagon 5 | 6 | 1 7 | relativeToGround 8 | 9 | 10 | 11 | -77.05788457660967,38.87253259892824,100 12 | -77.05465973756702,38.87291016281703,100 13 | -77.05315536854791,38.87053267794386,100 14 | -77.05552622493516,38.868757801256,100 15 | -77.05844056290393,38.86996206506943,100 16 | -77.05788457660967,38.87253259892824,100 17 | 18 | 19 | 20 | 21 | 22 | 23 | -77.05668055019126,38.87154239798456,100 24 | -77.05542625960818,38.87167890344077,100 25 | -77.05485125901024,38.87076535397792,100 26 | -77.05577677433152,38.87008686581446,100 27 | -77.05691162017543,38.87054446963351,100 28 | -77.05668055019126,38.87154239798456,100 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /tests/input/point.georss: -------------------------------------------------------------------------------- 1 | 2 | 4 | Earthquakes 5 | International earthquake observation labs 6 | 7 | 2005-12-13T18:30:02Z 8 | 9 | Dr. Thaddeus Remor 10 | tremor@quakelab.edu 11 | 12 | urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6 13 | 14 | M 3.2, Mona Passage 15 | 16 | urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a 17 | 2005-08-17T07:02:32Z 18 | We just had a big one. 19 | 45.256 -71.92 20 | 21 | -------------------------------------------------------------------------------- /tests/input/point.kml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Simple placemark 5 | Attached to the ground. Intelligently places itself 6 | at the height of the underlying terrain. 7 | 8 | -122.0822035425683,37.42228990140251,0 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/input/point.wkt: -------------------------------------------------------------------------------- 1 | POINT (10 12) 2 | -------------------------------------------------------------------------------- /tests/input/polygon.georss: -------------------------------------------------------------------------------- 1 | 2 | 45.256 -110.45 46.46 -109.48 43.84 -109.86 45.256 -110.45 3 | 4 | -------------------------------------------------------------------------------- /tests/input/polygon.wkt: -------------------------------------------------------------------------------- 1 | POLYGON ((30 10, 10 20, 20 40, 40 40, 30 10)) 2 | -------------------------------------------------------------------------------- /tests/input/polygon2.wkt: -------------------------------------------------------------------------------- 1 | POLYGON ((35 10, 10 20, 15 40, 45 45, 35 10), (20 30, 35 35, 30 20, 20 30)) 2 | -------------------------------------------------------------------------------- /tests/input/polygon3.wkt: -------------------------------------------------------------------------------- 1 | POLYGON ((-123.222653196 49.1529676585, -89.4726531957 49.3823707987, -81.0351531957 44.0875828344, -71.1914031957 44.3395630636, -62.0507781957 48.4583498573, -60.2929656957 45.0890334085, -78.9257781957 37.4399716272, -82.0898406957 31.3536343332, -81.3867156957 26.4312253295, -91.9335906957 29.8406412505, -98.2617156957 26.4312253295, -107.753903196 32.2499718728, -116.894528196 33.1375486348, -122.519528196 36.0313293064, -126.035153196 42.2935619329, -123.222653196 49.1529676585)) 2 | -------------------------------------------------------------------------------- /tests/input/polygon4.wkt: -------------------------------------------------------------------------------- 1 | POLYGON((4.8352495472368009 52.3561217600921438,4.8354139113045580 52.3561243429663534,4.8356082266282945 52.3561267417385281,4.8358010085903622 52.3561273083083663,4.8358010085903622 52.3561273083083663,4.8358035242637225 52.3559935212917722,4.8363777606561538 52.3559985348227173,4.8365863082998608 52.3560003600829731,4.8365523717335313 52.3570990145454189,4.8365884597636066 52.3572643433297529,4.8366320506970659 52.3574639095218686,4.8366736405531485 52.3576544056339870,4.8367264446828226 52.3578947700094304,4.8367922739966023 52.3581940807800450,4.8368228816936947 52.3583326871276356,4.8368228816936947 52.3583326871276356,4.8346348012064322 52.3583075969840550,4.8346348012064322 52.3583075969840550,4.8346348010943823 52.3583076059723282,4.8346348010943823 52.3583076059723282,4.8344931735728114 52.3583059732702338,4.8343773230572911 52.3583046496785585,4.8342182417472204 52.3583028092031384,4.8340047277034000 52.3583004442080195,4.8340047277034000 52.3583004442080195,4.8340047286008216 52.3583003723016063,4.8340047286008216 52.3583003723016063,4.8333843154510516 52.3582932434377639,4.8333843154510516 52.3582932434377639,4.8333915914677918 52.3580669388087898,4.8333968982183286 52.3578913129544787,4.8334415565569193 52.3563602568407660,4.8336003450092706 52.3563614767834267,4.8336013166539615 52.3563318721204567,4.8336013166539615 52.3563318721204567,4.8339582156582548 52.3563361223319603,4.8339656498645338 52.3561015845598732,4.8340692910524092 52.3561032110135258,4.8340692910524092 52.3561032110135258,4.8345511248958477 52.3561107854074095,4.8345511248958477 52.3561107854074095,4.8345513450958055 52.3561107864365809,4.8345513450958055 52.3561107864365809,4.8346742584771087 52.3561127181661092,4.8346742584771087 52.3561127181661092,4.8347750227755597 52.3561143035917596,4.8347750227755597 52.3561143035917596,4.8352495472368009 52.3561217600921438)) 2 | -------------------------------------------------------------------------------- /tests/input/polygon_spaces.wkt: -------------------------------------------------------------------------------- 1 | 2 | POLYGON ((30 10 , 10 20 , 20 40 , 40 40 , 30 10 )) 3 | -------------------------------------------------------------------------------- /tests/input/short.geohash: -------------------------------------------------------------------------------- 1 | xpssc0 2 | -------------------------------------------------------------------------------- /tests/input/simple_point.json: -------------------------------------------------------------------------------- 1 | {"type": "Feature", "geometry": {"type": "Point", "coordinates": [-80.73029, 35.3936]}} -------------------------------------------------------------------------------- /tests/input/track.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Garmin International 7 | 8 | 9 | 10 | 11 | Example GPX Document 12 | 13 | 14 | 4.46 15 | 16 | 17 | 18 | 4.94 19 | 20 | 21 | 22 | 6.87 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /tests/postgis.php: -------------------------------------------------------------------------------- 1 | setSRID(4326); 38 | test_postgis($name, $format, $geometry, $connection, 'ewkb'); 39 | } 40 | } 41 | print "Testing Done!"; 42 | } 43 | 44 | function test_postgis($name, $type, $geom, $connection, $format) { 45 | global $table; 46 | 47 | // Let's insert into the database using GeomFromWKB 48 | $insert_string = pg_escape_bytea($geom->out($format)); 49 | pg_query($connection, "INSERT INTO $table (name, type, geom) values ('$name', '$type', GeomFromWKB('$insert_string'))"); 50 | 51 | // SELECT using asBinary PostGIS 52 | $result = pg_fetch_all(pg_query($connection, "SELECT asBinary(geom) as geom FROM $table WHERE name='$name'")); 53 | foreach ($result as $item) { 54 | $wkb = pg_unescape_bytea($item['geom']); // Make sure to unescape the hex blob 55 | $geom = geoPHP::load($wkb, $format); // We now a full geoPHP Geometry object 56 | } 57 | 58 | // SELECT and INSERT directly, with no wrapping functions 59 | $result = pg_fetch_all(pg_query($connection, "SELECT geom as geom FROM $table WHERE name='$name'")); 60 | foreach ($result as $item) { 61 | $wkb = pack('H*',$item['geom']); // Unpacking the hex blob 62 | $geom = geoPHP::load($wkb, $format); // We now have a geoPHP Geometry 63 | 64 | // Let's re-insert directly into postGIS 65 | // We need to unpack the WKB 66 | $unpacked = unpack('H*', $geom->out($format)); 67 | $insert_string = $unpacked[1]; 68 | pg_query($connection, "INSERT INTO $table (name, type, geom) values ('$name', '$type', '$insert_string')"); 69 | } 70 | 71 | // SELECT and INSERT using as EWKT (ST_GeomFromEWKT and ST_AsEWKT) 72 | $result = pg_fetch_all(pg_query($connection, "SELECT ST_AsEWKT(geom) as geom FROM $table WHERE name='$name'")); 73 | foreach ($result as $item) { 74 | $wkt = $item['geom']; // Make sure to unescape the hex blob 75 | $geom = geoPHP::load($wkt, 'ewkt'); // We now a full geoPHP Geometry object 76 | 77 | // Let's re-insert directly into postGIS 78 | $insert_string = $geom->out('ewkt'); 79 | pg_query($connection, "INSERT INTO $table (name, type, geom) values ('$name', '$type', ST_GeomFromEWKT('$insert_string'))"); 80 | } 81 | } 82 | 83 | -------------------------------------------------------------------------------- /tests/test.php: -------------------------------------------------------------------------------- 1 | area(); 47 | $geometry->boundary(); 48 | $geometry->envelope(); 49 | $geometry->getBBox(); 50 | $geometry->centroid(); 51 | $geometry->length(); 52 | $geometry->greatCircleLength(); 53 | $geometry->haversineLength(); 54 | $geometry->y(); 55 | $geometry->x(); 56 | $geometry->numGeometries(); 57 | $geometry->geometryN(1); 58 | $geometry->startPoint(); 59 | $geometry->endPoint(); 60 | $geometry->isRing(); 61 | $geometry->isClosed(); 62 | $geometry->numPoints(); 63 | $geometry->pointN(1); 64 | $geometry->exteriorRing(); 65 | $geometry->numInteriorRings(); 66 | $geometry->interiorRingN(1); 67 | $geometry->dimension(); 68 | $geometry->geometryType(); 69 | $geometry->SRID(); 70 | $geometry->setSRID(4326); 71 | 72 | // Aliases 73 | $geometry->getCentroid(); 74 | $geometry->getArea(); 75 | $geometry->getX(); 76 | $geometry->getY(); 77 | $geometry->getGeos(); 78 | $geometry->getGeomType(); 79 | $geometry->getSRID(); 80 | $geometry->asText(); 81 | $geometry->asBinary(); 82 | 83 | // GEOS only functions 84 | $geometry->geos(); 85 | $geometry->setGeos($geometry->geos()); 86 | $geometry->pointOnSurface(); 87 | $geometry->equals($geometry); 88 | $geometry->equalsExact($geometry); 89 | $geometry->relate($geometry); 90 | $geometry->checkValidity(); 91 | $geometry->isSimple(); 92 | $geometry->buffer(10); 93 | $geometry->intersection($geometry); 94 | $geometry->convexHull(); 95 | $geometry->difference($geometry); 96 | $geometry->symDifference($geometry); 97 | $geometry->union($geometry); 98 | $geometry->simplify(0);// @@TODO: Adjust this once we can deal with empty geometries 99 | $geometry->disjoint($geometry); 100 | $geometry->touches($geometry); 101 | $geometry->intersects($geometry); 102 | $geometry->crosses($geometry); 103 | $geometry->within($geometry); 104 | $geometry->contains($geometry); 105 | $geometry->overlaps($geometry); 106 | $geometry->covers($geometry); 107 | $geometry->coveredBy($geometry); 108 | $geometry->distance($geometry); 109 | $geometry->hausdorffDistance($geometry); 110 | 111 | 112 | // Place holders 113 | $geometry->hasZ(); 114 | $geometry->is3D(); 115 | $geometry->isMeasured(); 116 | $geometry->isEmpty(); 117 | $geometry->coordinateDimension(); 118 | $geometry->z(); 119 | $geometry->m(); 120 | } 121 | 122 | function test_adapters($geometry, $format, $input) { 123 | // Test adapter output and input. Do a round-trip and re-test 124 | foreach (geoPHP::getAdapterMap() as $adapter_key => $adapter_class) { 125 | if ($adapter_key != 'google_geocode') { //Don't test google geocoder regularily. Uncomment to test 126 | $output = $geometry->out($adapter_key); 127 | if ($output) { 128 | $adapter_loader = new $adapter_class(); 129 | $test_geom_1 = $adapter_loader->read($output); 130 | $test_geom_2 = $adapter_loader->read($test_geom_1->out($adapter_key)); 131 | 132 | if ($test_geom_1->out('wkt') != $test_geom_2->out('wkt')) { 133 | print "Mismatched adapter output in ".$adapter_class."\n"; 134 | } 135 | } 136 | else { 137 | print "Empty output on " . $adapter_key . "\n"; 138 | } 139 | } 140 | } 141 | 142 | // Test to make sure adapter work the same wether GEOS is ON or OFF 143 | // Cannot test methods if GEOS is not intstalled 144 | if (!geoPHP::geosInstalled()) return; 145 | 146 | foreach (geoPHP::getAdapterMap() as $adapter_key => $adapter_class) { 147 | if ($adapter_key != 'google_geocode') { //Don't test google geocoder regularily. Uncomment to test 148 | // Turn GEOS on 149 | geoPHP::geosInstalled(TRUE); 150 | 151 | $output = $geometry->out($adapter_key); 152 | if ($output) { 153 | $adapter_loader = new $adapter_class(); 154 | 155 | $test_geom_1 = $adapter_loader->read($output); 156 | 157 | // Turn GEOS off 158 | geoPHP::geosInstalled(FALSE); 159 | 160 | $test_geom_2 = $adapter_loader->read($output); 161 | 162 | // Turn GEOS back On 163 | geoPHP::geosInstalled(TRUE); 164 | 165 | // Check to make sure a both are the same with geos and without 166 | if ($test_geom_1->out('wkt') != $test_geom_2->out('wkt')) { 167 | print "Mismatched adapter output between GEOS and NORM in ".$adapter_class."\n"; 168 | } 169 | } 170 | } 171 | } 172 | } 173 | 174 | 175 | function test_methods($geometry) { 176 | // Cannot test methods if GEOS is not intstalled 177 | if (!geoPHP::geosInstalled()) return; 178 | 179 | $methods = array( 180 | //'boundary', //@@TODO: Uncomment this and fix errors 181 | 'envelope', //@@TODO: Testing reveales errors in this method -- POINT vs. POLYGON 182 | 'getBBox', 183 | 'x', 184 | 'y', 185 | 'startPoint', 186 | 'endPoint', 187 | 'isRing', 188 | 'isClosed', 189 | 'numPoints', 190 | ); 191 | 192 | foreach ($methods as $method) { 193 | // Turn GEOS on 194 | geoPHP::geosInstalled(TRUE); 195 | $geos_result = $geometry->$method(); 196 | 197 | // Turn GEOS off 198 | geoPHP::geosInstalled(FALSE); 199 | $norm_result = $geometry->$method(); 200 | 201 | // Turn GEOS back On 202 | geoPHP::geosInstalled(TRUE); 203 | 204 | $geos_type = gettype($geos_result); 205 | $norm_type = gettype($norm_result); 206 | 207 | if ($geos_type != $norm_type) { 208 | print 'Type mismatch on '.$method."\n"; 209 | continue; 210 | } 211 | 212 | // Now check base on type 213 | if ($geos_type == 'object') { 214 | $haus_dist = $geos_result->hausdorffDistance(geoPHP::load($norm_result->out('wkt'),'wkt')); 215 | 216 | // Get the length of the diagonal of the bbox - this is used to scale the haustorff distance 217 | // Using Pythagorean theorem 218 | $bb = $geos_result->getBBox(); 219 | $scale = sqrt((($bb['maxy'] - $bb['miny'])^2) + (($bb['maxx'] - $bb['minx'])^2)); 220 | 221 | // The difference in the output of GEOS and native-PHP methods should be less than 0.5 scaled haustorff units 222 | if ($haus_dist / $scale > 0.5) { 223 | print 'Output mismatch on '.$method.":\n"; 224 | print 'GEOS : '.$geos_result->out('wkt')."\n"; 225 | print 'NORM : '.$norm_result->out('wkt')."\n"; 226 | continue; 227 | } 228 | } 229 | 230 | if ($geos_type == 'boolean' || $geos_type == 'string') { 231 | if ($geos_result !== $norm_result) { 232 | print 'Output mismatch on '.$method.":\n"; 233 | print 'GEOS : '.(string) $geos_result."\n"; 234 | print 'NORM : '.(string) $norm_result."\n"; 235 | continue; 236 | } 237 | } 238 | 239 | //@@TODO: Run tests for output of types arrays and float 240 | //@@TODO: centroid function is non-compliant for collections and strings 241 | } 242 | } 243 | 244 | function test_detection($value, $format, $file) { 245 | $detected = geoPHP::detectFormat($value); 246 | if ($detected != $format) { 247 | if ($detected) print 'detected as ' . $detected . "\n"; 248 | else print "format not detected\n"; 249 | } 250 | // Make sure it loads using auto-detect 251 | geoPHP::load($value); 252 | } 253 | 254 | function FailOnError($error_level, $error_message, $error_file, $error_line, $error_context) { 255 | echo "$error_level: $error_message in $error_file on line $error_line\n"; 256 | echo "\e[31m" . "FAIL" . "\e[39m\n"; 257 | exit(1); 258 | } -------------------------------------------------------------------------------- /tests/tests/20120702Test.php: -------------------------------------------------------------------------------- 1 | 'area'), 16 | array('name' => 'boundary'), 17 | array('name' => 'getBBox'), 18 | array('name' => 'centroid'), 19 | array('name' => 'length'), 20 | array('name' => 'greatCircleLength', 'argument' => 6378137), 21 | array('name' => 'haversineLength'), 22 | array('name' => 'y'), 23 | array('name' => 'x'), 24 | array('name' => 'numGeometries'), 25 | array('name' => 'geometryN', 'argument' => '1'), 26 | array('name' => 'startPoint'), 27 | array('name' => 'endPoint'), 28 | array('name' => 'isRing'), 29 | array('name' => 'isClosed'), 30 | array('name' => 'numPoints'), 31 | array('name' => 'pointN', 'argument' => '1'), 32 | array('name' => 'exteriorRing'), 33 | array('name' => 'numInteriorRings'), 34 | array('name' => 'interiorRingN', 'argument' => '1'), 35 | array('name' => 'dimension'), 36 | array('name' => 'geometryType'), 37 | array('name' => 'SRID'), 38 | array('name' => 'setSRID', 'argument' => '4326'), 39 | ); 40 | 41 | foreach($methods as $method) { 42 | $argument = NULL; 43 | $method_name = $method['name']; 44 | if (isset($method['argument'])) { 45 | $argument = $method['argument']; 46 | } 47 | $this->_methods_tester($geometry, $method_name, $argument); 48 | } 49 | } 50 | 51 | function _methods_tester($geometry, $method_name, $argument) { 52 | 53 | if (!method_exists($geometry, $method_name)) { 54 | $this->fail("Method ".$method_name.'() doesn\'t exists.'); 55 | return; 56 | } 57 | 58 | switch ($method_name) { 59 | case 'y': 60 | case 'x': 61 | if ($geometry->geometryType() == 'Point') { 62 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 63 | } 64 | if ($geometry->geometryType() == 'LineString') { 65 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 66 | } 67 | if ($geometry->geometryType() == 'MultiLineString') { 68 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 69 | } 70 | break; 71 | case 'geometryN': 72 | if ($geometry->geometryType() == 'Point') { 73 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 74 | } 75 | if ($geometry->geometryType() == 'LineString') { 76 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 77 | } 78 | if ($geometry->geometryType() == 'MultiLineString') { 79 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 80 | } 81 | break; 82 | case 'startPoint': 83 | if ($geometry->geometryType() == 'Point') { 84 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 85 | } 86 | if ($geometry->geometryType() == 'LineString') { 87 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 88 | } 89 | if ($geometry->geometryType() == 'MultiLineString') { 90 | //TODO: Add a method startPoint() to MultiLineString. 91 | //$this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 92 | } 93 | break; 94 | case 'endPoint': 95 | if ($geometry->geometryType() == 'Point') { 96 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 97 | } 98 | if ($geometry->geometryType() == 'LineString') { 99 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 100 | } 101 | if ($geometry->geometryType() == 'MultiLineString') { 102 | //TODO: Add a method endPoint() to MultiLineString. 103 | //$this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 104 | } 105 | break; 106 | case 'isRing': 107 | if ($geometry->geometryType() == 'Point') { 108 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 109 | } 110 | if ($geometry->geometryType() == 'LineString') { 111 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 112 | } 113 | if ($geometry->geometryType() == 'MultiLineString') { 114 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 115 | } 116 | break; 117 | case 'isClosed': 118 | if ($geometry->geometryType() == 'Point') { 119 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 120 | } 121 | if ($geometry->geometryType() == 'LineString') { 122 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 123 | } 124 | if ($geometry->geometryType() == 'MultiLineString') { 125 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 126 | } 127 | break; 128 | case 'pointN': 129 | if ($geometry->geometryType() == 'Point') { 130 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 131 | } 132 | if ($geometry->geometryType() == 'LineString') { 133 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 134 | } 135 | if ($geometry->geometryType() == 'MultiLineString') { 136 | //TODO: Add a method pointN() to MultiLineString. 137 | //$this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 138 | } 139 | break; 140 | case 'exteriorRing': 141 | if ($geometry->geometryType() == 'Point') { 142 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 143 | } 144 | if ($geometry->geometryType() == 'LineString') { 145 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 146 | } 147 | if ($geometry->geometryType() == 'MultiLineString') { 148 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 149 | } 150 | break; 151 | case 'numInteriorRings': 152 | if ($geometry->geometryType() == 'Point') { 153 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 154 | } 155 | if ($geometry->geometryType() == 'LineString') { 156 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 157 | } 158 | if ($geometry->geometryType() == 'MultiLineString') { 159 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 160 | } 161 | break; 162 | case 'interiorRingN': 163 | if ($geometry->geometryType() == 'Point') { 164 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 165 | } 166 | if ($geometry->geometryType() == 'LineString') { 167 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 168 | } 169 | if ($geometry->geometryType() == 'MultiLineString') { 170 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 171 | } 172 | break; 173 | case 'setSRID': 174 | //TODO: The method setSRID() should return TRUE. 175 | break; 176 | case 'SRID': 177 | if ($geometry->geometryType() == 'Point') { 178 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 179 | } 180 | if ($geometry->geometryType() == 'LineString') { 181 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 182 | } 183 | if ($geometry->geometryType() == 'MultiLineString') { 184 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 185 | } 186 | break; 187 | case 'getBBox': 188 | if ($geometry->geometryType() == 'Point') { 189 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 190 | } 191 | if ($geometry->geometryType() == 'LineString') { 192 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 193 | } 194 | if ($geometry->geometryType() == 'MultiLineString') { 195 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 196 | } 197 | break; 198 | case 'centroid': 199 | if ($geometry->geometryType() == 'Point') { 200 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 201 | } 202 | if ($geometry->geometryType() == 'LineString') { 203 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 204 | } 205 | if ($geometry->geometryType() == 'MultiLineString') { 206 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 207 | } 208 | break; 209 | case 'length': 210 | if ($geometry->geometryType() == 'Point') { 211 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 212 | } 213 | if ($geometry->geometryType() == 'LineString') { 214 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 215 | } 216 | if ($geometry->geometryType() == 'MultiLineString') { 217 | $this->assertEquals($geometry->$method_name($argument), (float) '0.11624637315233', 'Failed on ' . $method_name); 218 | } 219 | break; 220 | case 'numGeometries': 221 | if ($geometry->geometryType() == 'Point') { 222 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 223 | } 224 | if ($geometry->geometryType() == 'LineString') { 225 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 226 | } 227 | if ($geometry->geometryType() == 'MultiLineString') { 228 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 229 | } 230 | break; 231 | case 'numPoints': 232 | if ($geometry->geometryType() == 'Point') { 233 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 234 | } 235 | if ($geometry->geometryType() == 'LineString') { 236 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 237 | } 238 | if ($geometry->geometryType() == 'MultiLineString') { 239 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 240 | } 241 | break; 242 | case 'dimension': 243 | if ($geometry->geometryType() == 'Point') { 244 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 245 | } 246 | if ($geometry->geometryType() == 'LineString') { 247 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 248 | } 249 | if ($geometry->geometryType() == 'MultiLineString') { 250 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 251 | } 252 | break; 253 | case 'boundary': 254 | if ($geometry->geometryType() == 'Point') { 255 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 256 | } 257 | if ($geometry->geometryType() == 'LineString') { 258 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 259 | } 260 | if ($geometry->geometryType() == 'MultiLineString') { 261 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 262 | } 263 | break; 264 | case 'greatCircleLength': 265 | if ($geometry->geometryType() == 'Point') { 266 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 267 | } 268 | if ($geometry->geometryType() == 'LineString') { 269 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 270 | } 271 | if ($geometry->geometryType() == 'MultiLineString') { 272 | $this->assertNotEquals($geometry->$method_name($argument), '9500.9359867418', 'Failed on ' . $method_name); 273 | } 274 | break; 275 | case 'haversineLength': 276 | case 'area': 277 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 278 | break; 279 | case 'geometryType': 280 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 281 | break; 282 | default: 283 | $this->assertTrue($geometry->$method_name($argument), 'Failed on ' . $method_name); 284 | } 285 | } 286 | } 287 | 288 | -------------------------------------------------------------------------------- /tests/tests/adaptersTest.php: -------------------------------------------------------------------------------- 1 | $adapter_class) { 20 | if ($adapter_key != 'google_geocode') { //Don't test google geocoder regularily. Uncomment to test 21 | $output = $geometry->out($adapter_key); 22 | $this->assertNotNull($output, "Empty output on " . $adapter_key); 23 | if ($output) { 24 | $adapter_loader = new $adapter_class(); 25 | $test_geom_1 = $adapter_loader->read($output); 26 | $test_geom_2 = $adapter_loader->read($test_geom_1->out($adapter_key)); 27 | $this->assertEquals($test_geom_1->out('wkt'), $test_geom_2->out('wkt'), "Mismatched adapter output in ".$adapter_class .' (test file: ' . $file . ')'); 28 | } 29 | } 30 | } 31 | 32 | // Test to make sure adapter work the same wether GEOS is ON or OFF 33 | // Cannot test methods if GEOS is not intstalled 34 | if (!geoPHP::geosInstalled()) return; 35 | 36 | foreach (geoPHP::getAdapterMap() as $adapter_key => $adapter_class) { 37 | if ($adapter_key != 'google_geocode') { //Don't test google geocoder regularily. Uncomment to test 38 | // Turn GEOS on 39 | geoPHP::geosInstalled(TRUE); 40 | 41 | $output = $geometry->out($adapter_key); 42 | if ($output) { 43 | $adapter_loader = new $adapter_class(); 44 | 45 | $test_geom_1 = $adapter_loader->read($output); 46 | 47 | // Turn GEOS off 48 | geoPHP::geosInstalled(FALSE); 49 | 50 | $test_geom_2 = $adapter_loader->read($output); 51 | 52 | // Turn GEOS back On 53 | geoPHP::geosInstalled(TRUE); 54 | 55 | // Check to make sure a both are the same with geos and without 56 | $this->assertEquals($test_geom_1->out('wkt'), $test_geom_2->out('wkt'), "Mismatched adapter output between GEOS and NORM in ".$adapter_class .' (test file: ' . $file . ')'); 57 | } 58 | } 59 | } 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | assertEquals ( 'xne', $geohash->adjacent ( 'xn7', 'top' ), 'Did not find correct top adjacent geohash for xn7' ); 15 | $this->assertEquals ( 'xnk', $geohash->adjacent ( 'xn7', 'right' ), 'Did not find correct right adjacent geohash for xn7' ); 16 | $this->assertEquals ( 'xn5', $geohash->adjacent ( 'xn7', 'bottom' ), 'Did not find correct bottom adjacent geohash for xn7' ); 17 | $this->assertEquals ( 'xn6', $geohash->adjacent ( 'xn7', 'left' ), 'Did not find correct left adjacent geohash for xn7' ); 18 | $this->assertEquals ( 'xnd', $geohash->adjacent ( $geohash->adjacent ( 'xn7', 'left' ), 'top' ), 'Did not find correct top-left adjacent geohash for xn7' ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/tests/geosTest.php: -------------------------------------------------------------------------------- 1 | 'geos'), 24 | array('name' => 'setGeos', 'argument' => $geometry->geos()), 25 | array('name' => 'PointOnSurface'), 26 | array('name' => 'equals', 'argument' => $geometry), 27 | array('name' => 'equalsExact', 'argument' => $geometry), 28 | array('name' => 'relate', 'argument' => $geometry), 29 | array('name' => 'checkValidity'), 30 | array('name' => 'isSimple'), 31 | array('name' => 'buffer', 'argument' => '10'), 32 | array('name' => 'intersection', 'argument' => $geometry), 33 | array('name' => 'convexHull'), 34 | array('name' => 'difference', 'argument' => $geometry), 35 | array('name' => 'symDifference', 'argument' => $geometry), 36 | array('name' => 'union', 'argument' => $geometry), 37 | array('name' => 'simplify', 'argument' => '0'), 38 | array('name' => 'disjoint', 'argument' => $geometry), 39 | array('name' => 'touches', 'argument' => $geometry), 40 | array('name' => 'intersects', 'argument' => $geometry), 41 | array('name' => 'crosses', 'argument' => $geometry), 42 | array('name' => 'within', 'argument' => $geometry), 43 | array('name' => 'contains', 'argument' => $geometry), 44 | array('name' => 'overlaps', 'argument' => $geometry), 45 | array('name' => 'covers', 'argument' => $geometry), 46 | array('name' => 'coveredBy', 'argument' => $geometry), 47 | array('name' => 'distance', 'argument' => $geometry), 48 | array('name' => 'hausdorffDistance', 'argument' => $geometry), 49 | ); 50 | 51 | foreach($geosMethods as $method) { 52 | $argument = NULL; 53 | $method_name = $method['name']; 54 | if (isset($method['argument'])) { 55 | $argument = $method['argument']; 56 | } 57 | 58 | switch ($method_name) { 59 | case 'isSimple': 60 | case 'equals': 61 | case 'geos': 62 | if ($geometry->geometryType() == 'Point') { 63 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 64 | } 65 | if ($geometry->geometryType() == 'LineString') { 66 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 67 | } 68 | if ($geometry->geometryType() == 'MultiLineString') { 69 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 70 | } 71 | break; 72 | default: 73 | if ($geometry->geometryType() == 'Point') { 74 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 75 | } 76 | if ($geometry->geometryType() == 'LineString') { 77 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 78 | } 79 | if ($geometry->geometryType() == 'MultiLineString') { 80 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 81 | } 82 | } 83 | } 84 | 85 | } 86 | } 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /tests/tests/methodsTest.php: -------------------------------------------------------------------------------- 1 | 'area'), 20 | array('name' => 'boundary'), 21 | array('name' => 'getBBox'), 22 | array('name' => 'centroid'), 23 | array('name' => 'length'), 24 | array('name' => 'greatCircleLength'), 25 | array('name' => 'haversineLength'), 26 | array('name' => 'y'), 27 | array('name' => 'x'), 28 | array('name' => 'numGeometries'), 29 | array('name' => 'geometryN', 'argument' => '1'), 30 | array('name' => 'startPoint'), 31 | array('name' => 'endPoint'), 32 | array('name' => 'isRing'), 33 | array('name' => 'isClosed'), 34 | array('name' => 'numPoints'), 35 | array('name' => 'pointN', 'argument' => '1'), 36 | array('name' => 'exteriorRing'), 37 | array('name' => 'numInteriorRings'), 38 | array('name' => 'interiorRingN', 'argument' => '1'), 39 | array('name' => 'dimension'), 40 | array('name' => 'geometryType'), 41 | array('name' => 'SRID'), 42 | array('name' => 'setSRID', 'argument' => '4326'), 43 | ); 44 | 45 | foreach($methods as $method) { 46 | $argument = NULL; 47 | $method_name = $method['name']; 48 | if (isset($method['argument'])) { 49 | $argument = $method['argument']; 50 | } 51 | 52 | $this->_methods_tester($geometry, $method_name, $argument, $file); 53 | } 54 | 55 | $this->_methods_tester_with_geos($geometry); 56 | } 57 | } 58 | } 59 | 60 | function _methods_tester($geometry, $method_name, $argument, $file) { 61 | 62 | if (!method_exists($geometry, $method_name)) { 63 | $this->fail("Method ".$method_name.'() doesn\'t exists.'); 64 | return; 65 | } 66 | 67 | switch ($method_name) { 68 | case 'y': 69 | case 'x': 70 | if (!$geometry->isEmpty()) { 71 | if ($geometry->geometryType() == 'Point') { 72 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 73 | } 74 | if ($geometry->geometryType() == 'LineString') { 75 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 76 | } 77 | if ($geometry->geometryType() == 'MultiLineString') { 78 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 79 | } 80 | } 81 | break; 82 | case 'geometryN': 83 | if ($geometry->geometryType() == 'Point') { 84 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 85 | } 86 | if ($geometry->geometryType() == 'LineString') { 87 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 88 | } 89 | if ($geometry->geometryType() == 'MultiLineString') { 90 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 91 | } 92 | break; 93 | case 'startPoint': 94 | if ($geometry->geometryType() == 'Point') { 95 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 96 | } 97 | if ($geometry->geometryType() == 'LineString') { 98 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 99 | } 100 | if ($geometry->geometryType() == 'MultiLineString') { 101 | //TODO: Add a method startPoint() to MultiLineString. 102 | //$this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 103 | } 104 | break; 105 | case 'endPoint': 106 | if ($geometry->geometryType() == 'Point') { 107 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 108 | } 109 | if ($geometry->geometryType() == 'LineString') { 110 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 111 | } 112 | if ($geometry->geometryType() == 'MultiLineString') { 113 | //TODO: Add a method endPoint() to MultiLineString. 114 | //$this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name); 115 | } 116 | break; 117 | case 'isRing': 118 | if ($geometry->geometryType() == 'Point') { 119 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 120 | } 121 | if ($geometry->geometryType() == 'LineString') { 122 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 123 | } 124 | if ($geometry->geometryType() == 'MultiLineString') { 125 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 126 | } 127 | break; 128 | case 'isClosed': 129 | if ($geometry->geometryType() == 'Point') { 130 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 131 | } 132 | if ($geometry->geometryType() == 'LineString') { 133 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 134 | } 135 | if ($geometry->geometryType() == 'MultiLineString') { 136 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 137 | } 138 | break; 139 | case 'pointN': 140 | if ($geometry->geometryType() == 'Point') { 141 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 142 | } 143 | if ($geometry->geometryType() == 'LineString') { 144 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 145 | } 146 | if ($geometry->geometryType() == 'MultiLineString') { 147 | //TODO: Add a method pointN() to MultiLineString. 148 | //$this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 149 | } 150 | break; 151 | case 'exteriorRing': 152 | if ($geometry->geometryType() == 'Point') { 153 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 154 | } 155 | if ($geometry->geometryType() == 'LineString') { 156 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 157 | } 158 | if ($geometry->geometryType() == 'MultiLineString') { 159 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 160 | } 161 | break; 162 | case 'numInteriorRings': 163 | if ($geometry->geometryType() == 'Point') { 164 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 165 | } 166 | if ($geometry->geometryType() == 'LineString') { 167 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 168 | } 169 | if ($geometry->geometryType() == 'MultiLineString') { 170 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 171 | } 172 | break; 173 | case 'interiorRingN': 174 | if ($geometry->geometryType() == 'Point') { 175 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 176 | } 177 | if ($geometry->geometryType() == 'LineString') { 178 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 179 | } 180 | if ($geometry->geometryType() == 'MultiLineString') { 181 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 182 | } 183 | break; 184 | case 'SRID': 185 | break; 186 | case 'getBBox': 187 | if (!$geometry->isEmpty()) { 188 | if ($geometry->geometryType() == 'Point') { 189 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 190 | } 191 | if ($geometry->geometryType() == 'LineString') { 192 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 193 | } 194 | if ($geometry->geometryType() == 'MultiLineString') { 195 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 196 | } 197 | } 198 | break; 199 | case 'centroid': 200 | if ($geometry->geometryType() == 'Point') { 201 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 202 | } 203 | if ($geometry->geometryType() == 'LineString') { 204 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 205 | } 206 | if ($geometry->geometryType() == 'MultiLineString') { 207 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 208 | } 209 | break; 210 | case 'length': 211 | if ($geometry->geometryType() == 'Point') { 212 | $this->assertEquals($geometry->$method_name($argument), 0, 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 213 | } 214 | if ($geometry->geometryType() == 'LineString') { 215 | $this->assertNotEquals($geometry->$method_name($argument), 0, 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 216 | } 217 | if ($geometry->geometryType() == 'MultiLineString') { 218 | $this->assertNotEquals($geometry->$method_name($argument), 0, 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 219 | } 220 | break; 221 | case 'numGeometries': 222 | if ($geometry->geometryType() == 'Point') { 223 | $this->assertNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 224 | } 225 | if ($geometry->geometryType() == 'LineString') { 226 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 227 | } 228 | if ($geometry->geometryType() == 'MultiLineString') { 229 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 230 | } 231 | break; 232 | case 'numPoints': 233 | if ($geometry->geometryType() == 'Point') { 234 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 235 | } 236 | if ($geometry->geometryType() == 'LineString') { 237 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 238 | } 239 | if ($geometry->geometryType() == 'MultiLineString') { 240 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 241 | } 242 | break; 243 | case 'dimension': 244 | if ($geometry->geometryType() == 'Point') { 245 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 246 | } 247 | if ($geometry->geometryType() == 'LineString') { 248 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 249 | } 250 | if ($geometry->geometryType() == 'MultiLineString') { 251 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 252 | } 253 | break; 254 | case 'boundary': 255 | if ($geometry->geometryType() == 'Point') { 256 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 257 | } 258 | if ($geometry->geometryType() == 'LineString') { 259 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 260 | } 261 | if ($geometry->geometryType() == 'MultiLineString') { 262 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 263 | } 264 | break; 265 | case 'haversineLength': 266 | //TODO: Check if output is a float >= 0. 267 | //TODO: Sometimes haversineLength() returns NAN, needs to check why. 268 | break; 269 | case 'greatCircleLength': 270 | case 'area': 271 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 272 | break; 273 | case 'geometryType': 274 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 275 | break; 276 | case 'setSRID': 277 | //TODO: The method setSRID() should return TRUE. 278 | break; 279 | default: 280 | $this->assertTrue($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 281 | } 282 | 283 | } 284 | 285 | function _methods_tester_with_geos($geometry) { 286 | // Cannot test methods if GEOS is not intstalled 287 | if (!geoPHP::geosInstalled()) return; 288 | 289 | $methods = array( 290 | //'boundary', //@@TODO: Uncomment this and fix errors 291 | 'envelope', //@@TODO: Testing reveales errors in this method -- POINT vs. POLYGON 292 | 'getBBox', 293 | 'x', 294 | 'y', 295 | 'startPoint', 296 | 'endPoint', 297 | 'isRing', 298 | 'isClosed', 299 | 'numPoints', 300 | ); 301 | 302 | foreach ($methods as $method) { 303 | // Turn GEOS on 304 | geoPHP::geosInstalled(TRUE); 305 | $geos_result = $geometry->$method(); 306 | 307 | // Turn GEOS off 308 | geoPHP::geosInstalled(FALSE); 309 | $norm_result = $geometry->$method(); 310 | 311 | // Turn GEOS back On 312 | geoPHP::geosInstalled(TRUE); 313 | 314 | $geos_type = gettype($geos_result); 315 | $norm_type = gettype($norm_result); 316 | 317 | if ($geos_type != $norm_type) { 318 | $this->fail('Type mismatch on '.$method); 319 | $this->dump($geos_type); 320 | $this->dump($norm_type); 321 | continue; 322 | } 323 | 324 | // Now check base on type 325 | if ($geos_type == 'object') { 326 | $haus_dist = $geos_result->hausdorffDistance(geoPHP::load($norm_result->out('wkt'),'wkt')); 327 | 328 | // Get the length of the diagonal of the bbox - this is used to scale the haustorff distance 329 | // Using Pythagorean theorem 330 | $bb = $geos_result->getBBox(); 331 | $scale = sqrt((($bb['maxy'] - $bb['miny'])^2) + (($bb['maxx'] - $bb['minx'])^2)); 332 | 333 | // The difference in the output of GEOS and native-PHP methods should be less than 0.5 scaled haustorff units 334 | if ($haus_dist / $scale > 0.5) { 335 | $this->fail('Output mismatch on '.$method); 336 | $this->dump('GEOS : '); 337 | $this->dump($geos_result->out('wkt')); 338 | $this->dump('NORM : '); 339 | $this->dump($norm_result->out('wkt')); 340 | continue; 341 | } 342 | } 343 | 344 | if ($geos_type == 'boolean' || $geos_type == 'string') { 345 | if ($geos_result !== $norm_result) { 346 | $this->fail('Output mismatch on '.$method); 347 | $this->dump('GEOS : '); 348 | $this->dump((string) $geos_result); 349 | $this->dump('NORM : '); 350 | $this->dump((string) $norm_result); 351 | continue; 352 | } 353 | } 354 | 355 | //@@TODO: Run tests for output of types arrays and float 356 | //@@TODO: centroid function is non-compliant for collections and strings 357 | } 358 | } 359 | } 360 | -------------------------------------------------------------------------------- /tests/tests/placeholdersTest.php: -------------------------------------------------------------------------------- 1 | 'hasZ'), 20 | array('name' => 'is3D'), 21 | array('name' => 'isMeasured'), 22 | array('name' => 'isEmpty'), 23 | array('name' => 'coordinateDimension'), 24 | array('name' => 'z'), 25 | array('name' => 'm'), 26 | ); 27 | 28 | foreach($placeholders as $method) { 29 | $argument = NULL; 30 | $method_name = $method['name']; 31 | if (isset($method['argument'])) { 32 | $argument = $method['argument']; 33 | } 34 | 35 | switch ($method_name) { 36 | case 'hasZ': 37 | if ($geometry->geometryType() == 'Point') { 38 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 39 | } 40 | if ($geometry->geometryType() == 'LineString') { 41 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 42 | } 43 | if ($geometry->geometryType() == 'MultiLineString') { 44 | $this->assertNotNull($geometry->$method_name($argument), 'Failed on ' . $method_name .' (test file: ' . $file . ')'); 45 | } 46 | break; 47 | case 'm': 48 | case 'z': 49 | case 'coordinateDimension': 50 | case 'isEmpty': 51 | case 'isMeasured': 52 | case 'is3D': 53 | } 54 | } 55 | 56 | } 57 | } 58 | 59 | } 60 | } 61 | --------------------------------------------------------------------------------