├── .github └── workflows │ ├── build.yml │ ├── compile.sh │ └── show-errors.sh ├── .gitignore ├── CREDITS ├── EXPERIMENTAL ├── LICENSE ├── README.rst ├── config.m4 ├── config.w32 ├── geo_array.c ├── geo_array.h ├── geo_lat_long.h ├── geohash.c ├── geohash.h ├── geospatial.c ├── package.xml ├── php_geospatial.h ├── php_geospatial.stub.php ├── php_geospatial_arginfo.h ├── rebuild-32bit.sh ├── rebuild.sh └── tests ├── Greenwich.phpt ├── JodrellBank.phpt ├── OSGB36_to_WGS84.phpt ├── WGS84_to_OSGB36.phpt ├── bug0016.phpt ├── cartesian_to_polar.phpt ├── decimal_to_dms.phpt ├── dms_to_decimal.phpt ├── fraction_along-001.phpt ├── fraction_along-002.phpt ├── fraction_along-003.phpt ├── geohash_decode.phpt ├── geohash_encode.phpt ├── geospatial_haversine_london_edinburgh.phpt ├── geospatial_haversine_polar_distance.phpt ├── haversine.phpt ├── helmert.phpt ├── initial_bearing1.phpt ├── initial_bearing2.phpt ├── interpolate-line-string-001.phpt ├── interpolate-line-string-002.phpt ├── interpolate-line-string-003.phpt ├── polar_to_cartesian.phpt ├── rdp-belgium.json ├── rdp_simplify.phpt └── vincenty.phpt /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | tests: 12 | runs-on: ubuntu-latest 13 | name: Build and test 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | php: [8.0, 8.1, 8.2, 8.3] 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | 22 | - name: Setup PHP 23 | uses: shivammathur/setup-php@v2 24 | with: 25 | php-version: "${{ matrix.php }}" 26 | coverage: none 27 | ini-values: "session.save_path=/tmp" 28 | extensions: json 29 | tools: pecl 30 | 31 | - name: Compile 32 | run: ./.github/workflows/compile.sh 33 | 34 | - name: Run tests 35 | run: | 36 | php -m 37 | php --rf "json_decode" 38 | export TEST_PHP_EXECUTABLE=`which php` 39 | php run-tests.php -d extension=`pwd`/modules/geospatial.so && if ls tests/*.diff >/dev/null 2>&1; then echo "Tests failed" && cat tests/*.diff && exit 1; fi 40 | 41 | - name: Show errors 42 | if: ${{ failure() }} 43 | run: ./.github/workflows/show-errors.sh 44 | 45 | - name: Do GCOV 46 | run: | 47 | gcov --object-directory .libs *.c 48 | bash <(curl -s https://codecov.io/bash) 49 | -------------------------------------------------------------------------------- /.github/workflows/compile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | phpize 4 | EXTRA_LDFLAGS="-precious-files-regex .libs/geospatial.gcno" LDFLAGS="-lgcov" CFLAGS="-Wall -ggdb3 -fno-strict-aliasing -coverage -O0" ./configure --enable-geospatial 5 | make 6 | -------------------------------------------------------------------------------- /.github/workflows/show-errors.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | for i in tests/*.diff tests/*.sh; do 4 | echo "==========================================================================" 5 | echo $i 6 | echo "--------------------------------------------------------------------------" 7 | cat $i 8 | echo "==========================================================================" 9 | echo 10 | done 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.deps 2 | /Makefile 3 | /*.lo 4 | /*.loT 5 | /*.slo 6 | /*.mk 7 | /*.la 8 | /.libs 9 | /libs.mk 10 | /ac*.m4 11 | /build 12 | /config.h 13 | /config.h.in 14 | /config.nice 15 | /config.sub 16 | /configure 17 | /configure.ac 18 | /configure.in 19 | /config.status 20 | /config.cache 21 | /conftest 22 | /conftest.c 23 | /core 24 | /dynlib.m4 25 | /install-sh 26 | /ltmain.sh 27 | /include 28 | /Makefile.fragments 29 | /Makefile.global 30 | /Makefile.objects 31 | /missing 32 | /mkinstalldirs 33 | /modules 34 | /scan_makefile_in.awk 35 | /config.guess 36 | /*swp 37 | /config.log 38 | /libtool 39 | /Debug 40 | /Release 41 | /Debug_TS 42 | /Release_TS 43 | /*.plg 44 | /*.patch 45 | /*.tgz 46 | /*.ncb 47 | /*.opt 48 | /*.dsw 49 | /autom4te.cache 50 | /run-tests-config.php 51 | /run-tests.php 52 | /tmp-php.ini 53 | .svn 54 | tests/*.diff 55 | tests/*.exp 56 | tests/*.log 57 | tests/*.out 58 | tests/*.php 59 | tests/*.sh 60 | /*~ 61 | *.dep 62 | geospatial_arginfo.h 63 | -------------------------------------------------------------------------------- /CREDITS: -------------------------------------------------------------------------------- 1 | PHP geospatial extension 2 | 3 | Finally commenced at PHPNW12 4 | 5 | Marcus Deglos 6 | Michael Maclean 7 | Ryan Mauger 8 | 9 | -------------------------------------------------------------------------------- /EXPERIMENTAL: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/php-geospatial/geospatial/2f9bae665893d954260a4091f27203cb209ad58e/EXPERIMENTAL -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------- 2 | The PHP License, version 3.01 3 | Copyright (c) 1999 - 2015 The PHP Group. All rights reserved. 4 | -------------------------------------------------------------------- 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, is permitted provided that the following conditions 8 | are met: 9 | 10 | 1. Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | 13 | 2. Redistributions in binary form must reproduce the above copyright 14 | notice, this list of conditions and the following disclaimer in 15 | the documentation and/or other materials provided with the 16 | distribution. 17 | 18 | 3. The name "PHP" must not be used to endorse or promote products 19 | derived from this software without prior written permission. For 20 | written permission, please contact group@php.net. 21 | 22 | 4. Products derived from this software may not be called "PHP", nor 23 | may "PHP" appear in their name, without prior written permission 24 | from group@php.net. You may indicate that your software works in 25 | conjunction with PHP by saying "Foo for PHP" instead of calling 26 | it "PHP Foo" or "phpfoo" 27 | 28 | 5. The PHP Group may publish revised and/or new versions of the 29 | license from time to time. Each version will be given a 30 | distinguishing version number. 31 | Once covered code has been published under a particular version 32 | of the license, you may always continue to use it under the terms 33 | of that version. You may also choose to use such covered code 34 | under the terms of any subsequent version of the license 35 | published by the PHP Group. No one other than the PHP Group has 36 | the right to modify the terms applicable to covered code created 37 | under this License. 38 | 39 | 6. Redistributions of any form whatsoever must retain the following 40 | acknowledgment: 41 | "This product includes PHP software, freely available from 42 | ". 43 | 44 | THIS SOFTWARE IS PROVIDED BY THE PHP DEVELOPMENT TEAM ``AS IS'' AND 45 | ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 46 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 47 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PHP 48 | DEVELOPMENT TEAM OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 49 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 50 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 51 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 52 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 53 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 54 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 55 | OF THE POSSIBILITY OF SUCH DAMAGE. 56 | 57 | -------------------------------------------------------------------- 58 | 59 | This software consists of voluntary contributions made by many 60 | individuals on behalf of the PHP Group. 61 | 62 | The PHP Group can be contacted via Email at group@php.net. 63 | 64 | For more information on the PHP Group and the PHP project, 65 | please see . 66 | 67 | PHP includes the Zend Engine, freely available at 68 | . 69 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ===================================== 2 | geospatial - PHP Geospatial Extension 3 | ===================================== 4 | .. image:: https://travis-ci.org/php-geospatial/geospatial.svg?branch=master 5 | :target: https://travis-ci.org/php-geospatial/geospatial 6 | .. image:: https://codecov.io/gh/php-geospatial/geospatial/branch/master/graphs/badge.svg?branch=master 7 | :target: https://codecov.io/github/php-geospatial/geospatial?branch=master 8 | 9 | PHP Extension to handle common geospatial functions. The extension currently 10 | has implementations of the Haversine and Vincenty's formulas for calculating 11 | distances, an initial bearing calculation function, a Helmert transformation 12 | function to transfer between different supported datums, conversions between 13 | polar and Cartesian coordinates, conversions between Degree/Minute/Seconds and 14 | decimal degrees, a method to simplify linear geometries, as well as a method 15 | to calculate intermediate points on a LineString. 16 | 17 | Instalation 18 | =========== 19 | 20 | :: 21 | 22 | git clone git@github.com:php-geospatial/geospatial.git 23 | cd geospatial 24 | phpize 25 | ./configure --enable-geospatial 26 | make 27 | sudo make install 28 | 29 | Then add the extension to an ini file e.g. /etc/php.ini:: 30 | 31 | extension = geospatial.so 32 | 33 | Usage 34 | ===== 35 | 36 | The extension makes use of the GeoJSON standard format for specifying points as 37 | co-ordinates. One important thing to note about this format is that points are 38 | specified longitude **first** i.e. longitude, latitude. 39 | 40 | e.g.:: 41 | 42 | $greenwichObservatory = array( 43 | 'type' => 'Point', 44 | 'coordinates' => array( -0.001483 , 51.477917); 45 | ); 46 | 47 | 48 | Haversine 49 | --------- 50 | 51 | :: 52 | 53 | $from = array( 54 | 'type' => 'Point', 55 | 'coordinates' => array( -104.88544, 39.06546 ) 56 | ); 57 | $to = array( 58 | 'type' => 'Point', 59 | 'coordinates' => array( -104.80, 39.06546 ) 60 | ); 61 | var_dump(haversine($to, $from)); 62 | 63 | 64 | Vincenty's Formula 65 | ------------------ 66 | 67 | Vincenty's formula attempts to provide a more accurate distance between two 68 | points than the Haversine formula. Whereas the Haversine formula assumes a 69 | spherical earth the Vincenty method models the earth as an ellipsoid:: 70 | 71 | $flinders = array( 72 | 'type' => 'Point', 73 | 'coordinates' => array(144.42486788889, -37.951033416667 ) 74 | ); 75 | $buninyong = array( 76 | 'type' => 'Point', 77 | 'coordinates' => array(143.92649552778, -37.652821138889 ) 78 | ); 79 | var_dump(vincenty($flinders, $buninyong)); 80 | 81 | 82 | Helmert Transformation 83 | ---------------------- 84 | 85 | The Helmert transformation allows for the transformation of points between 86 | different datums. It can for instance be used to convert between the WGS84 87 | ellipsoid (GEO_WGS84) used by GPS systems and OSGB36 (GEO_AIRY_1830) used by 88 | Ordnance Survey in the UK:: 89 | 90 | $greenwichObservatory = array( 91 | 'type' => 'Point', 92 | 'coordinates' => array(-0.0014833333333333 , 51.477916666667) 93 | ); 94 | 95 | $greenwichObservatoryWGS84 = transform_datum($greenwichObservatory, GEO_WGS84, GEO_AIRY_1830); 96 | 97 | var_dump($greenwichObservatoryWGS84); 98 | 99 | Calculating Initial Bearing 100 | --------------------------- 101 | 102 | The ``initial_bearing`` function calculates the initial bearing to go from the 103 | first to the second point, as expressed in a GeoJSON wrapper:: 104 | 105 | $from = array( 106 | 'type' => 'Point', 107 | 'coordinates' => array( 2.351, 48.857 ) 108 | ); 109 | $to = array( 110 | 'type' => 'Point', 111 | 'coordinates' => array( 0.119, 52.205 ) 112 | ); 113 | var_dump(initial_bearing($from, $to)); 114 | 115 | The range of the resulting heading is 0° to +360°. 116 | 117 | Converting between polar and Cartesian Coordinates 118 | -------------------------------------------------- 119 | 120 | These two functions calculate between Polar and Cartesian Coordinates, 121 | with results depending on which ellipsoid you use. 122 | 123 | From Polar to Cartesian:: 124 | 125 | $lat = 53.38361111111; 126 | $long = 1.4669444444; 127 | 128 | var_dump(polar_to_cartesian($lat, $long, GEO_AIRY_1830)); 129 | 130 | And back:: 131 | 132 | $x = 3810891.6734396; 133 | $y = 97591.624686311; 134 | $z = 5095766.3939034; 135 | 136 | $polar = cartesian_to_polar($x, $y, $z, GEO_AIRY_1830); 137 | echo round($polar['lat'], 6), PHP_EOL; 138 | echo round($polar['long'], 6), PHP_EOL; 139 | echo round($polar['height'], 3), PHP_EOL; 140 | 141 | Converting between Degree/Min/Sec and Decimal coordinates 142 | --------------------------------------------------------- 143 | 144 | From decimal to dms. The second argument is either "longitude" or "latitude":: 145 | 146 | $dms = decimal_to_dms(-1.034291666667, 'longitude'); 147 | var_dump($dms); 148 | 149 | Which outputs:: 150 | 151 | array(4) { 152 | ["degrees"]=> int(1) 153 | ["minutes"]=> int(2) 154 | ["seconds"]=> float(3.4500000011994) 155 | ["direction"]=> string(1) "W" 156 | } 157 | 158 | And back from DMS to decimal, where the fourth argument is either "N", "S", 159 | "E", or "W":: 160 | 161 | $decimal = dms_to_decimal(0, 6, 9, 'S'); 162 | 163 | Which outputs:: 164 | 165 | float(-0.1025) 166 | 167 | Simplifying LineStrings 168 | ----------------------- 169 | 170 | The ``rdp_simplify`` method implements RDP_ to simplify a LineString 171 | according to a certain accuracy (epsilon). As first argument it takes a 172 | GeoJSON LineString (in PHP variable format), and it outputs a similar 173 | structure but then simplified 174 | 175 | .. _RDP: https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm 176 | 177 | Interpolation along a Greater Circle Line 178 | ----------------------------------------- 179 | 180 | The ``fraction_along_gc_line`` function can be used to calculate intermediate 181 | points along a Greater Circle Line. For example if you need to draw lines with 182 | more accuracy with for example Leaflet. The function takes the start and end 183 | coordinates (as GeoJson Point), and calculates the intermediate point along 184 | those line. To calculate the point 25% from the start point to the end point, 185 | you would use:: 186 | 187 | $point1 = [ 'type' => 'Point', 'coordinates' => [ 5, 10 ] ]; 188 | $point2 = [ 'type' => 'Point', 'coordinates' => [ 15, 10 ] ]; 189 | 190 | var_dump(fraction_along_gc_line($point1, $point2, 0.25)); 191 | 192 | Interpolating a GeoJSONLineString 193 | --------------------------------- 194 | 195 | The ``interpolate_linestring`` functions takes a GeoJSONLineString. If the 196 | Pythagorean distance in degrees between two points in the line than the 197 | ``epsilon`` value, it inserts a new point every ``epsilon / distance`` 198 | fraction of the Great Circle Line. 199 | 200 | Given the Linestring:: 201 | 202 | $lineString = [ 203 | 'type' => 'Linestring', 204 | 'coordinates' => [ 205 | [ 5, 10 ], 206 | [ 15, 10 ], 207 | [ 0, -50 ], 208 | ] 209 | ]; 210 | 211 | The following will return an array with 26 elements:: 212 | 213 | var_dump(interpolate_linestring($lineString, 3)); 214 | 215 | Five for the first pair, at fractions ``0.0``, ``0.3``, ``0.6``, ``0.9`` and 216 | ``1.0``, and then two times, each another ``0.0485`` fraction along the Great 217 | Circle Line for the second pair. 218 | 219 | Geohashing 220 | ---------- 221 | 222 | The `geohash_encode` function can be used to convert GeoJSON Point to a 223 | geohash of a specific length (in this case, 12):: 224 | 225 | $point = [ 'type' => 'Point', 'coordinates' => [ 16.4, 48.2 ] ]; 226 | echo geohash_encode( $point, 12 ); 227 | 228 | Which outputs:: 229 | 230 | u2edjnw17enr 231 | 232 | Similarly, a hashed coordinates pair can be decoded using `geohash_decode` 233 | function:: 234 | 235 | var_dump(geohash_decode('u2edjnw17enr')); 236 | array(2) { 237 | ["type"]=> 238 | string(5) "Point" 239 | ["coordinates"]=> 240 | array(2) { 241 | [0]=> 242 | float(16.40000006184) 243 | [1]=> 244 | float(48.199999993667) 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /config.m4: -------------------------------------------------------------------------------- 1 | dnl $Id$ 2 | dnl config.m4 for extension geospatial 3 | 4 | PHP_ARG_ENABLE(geospatial, whether to enable geospatial support, 5 | [ --enable-geospatial Enable geospatial support]) 6 | 7 | if test "$PHP_GEOSPATIAL" != "no"; then 8 | PHP_NEW_EXTENSION(geospatial, geospatial.c geo_array.c geohash.c, $ext_shared) 9 | fi 10 | -------------------------------------------------------------------------------- /config.w32: -------------------------------------------------------------------------------- 1 | // $Id$ 2 | // vim:ft=javascript 3 | 4 | ARG_ENABLE("geospatial", "enable geospatial support", "no"); 5 | 6 | if (PHP_GEOSPATIAL != "no") { 7 | EXTENSION("geospatial", "geospatial.c geo_array.c geohash.c"); 8 | } 9 | 10 | -------------------------------------------------------------------------------- /geo_array.c: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | PHP Version 5/7 | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) 1997-2016 The PHP Group | 6 | +----------------------------------------------------------------------+ 7 | | This source file is subject to version 3.01 of the PHP license, | 8 | | that is bundled with this package in the file LICENSE, and is | 9 | | available through the world-wide-web at the following url: | 10 | | http://www.php.net/license/3_01.txt | 11 | | If you did not receive a copy of the PHP license and are unable to | 12 | | obtain it through the world-wide-web, please send a note to | 13 | | license@php.net so we can mail you a copy immediately. | 14 | +----------------------------------------------------------------------+ 15 | | Authors: Derick Rethans | 16 | | Michael Maclean | 17 | | Nathaniel McHugh | 18 | | Marcus Deglos | 19 | +----------------------------------------------------------------------+ 20 | */ 21 | 22 | #include 23 | 24 | #include "geo_array.h" 25 | 26 | geo_array *geo_array_ctor(int element_count) 27 | { 28 | geo_array *tmp; 29 | 30 | tmp = malloc(sizeof(geo_array)); 31 | tmp->count = element_count; 32 | tmp->allocated = element_count; 33 | tmp->status = calloc(1, element_count); 34 | tmp->x = (double*) calloc(1, element_count * sizeof(double)); 35 | tmp->y = (double*) calloc(1, element_count * sizeof(double)); 36 | 37 | return tmp; 38 | } 39 | 40 | void geo_array_add(geo_array *points, double lat, double lon) 41 | { 42 | if (points->count >= points->allocated) { 43 | points->allocated = 1 + (points->allocated * 2); 44 | points->status = realloc(points->status, points->allocated); 45 | points->x = (double*) realloc(points->x, points->allocated * sizeof(double)); 46 | points->y = (double*) realloc(points->y, points->allocated * sizeof(double)); 47 | } 48 | points->x[points->count] = lat; 49 | points->y[points->count] = lon; 50 | points->status[points->count] = 1; 51 | 52 | points->count++; 53 | } 54 | 55 | void geo_array_dtor(geo_array *points) 56 | { 57 | free(points->status); 58 | free(points->x); 59 | free(points->y); 60 | free(points); 61 | } 62 | -------------------------------------------------------------------------------- /geo_array.h: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | PHP Version 5/7 | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) 1997-2016 The PHP Group | 6 | +----------------------------------------------------------------------+ 7 | | This source file is subject to version 3.01 of the PHP license, | 8 | | that is bundled with this package in the file LICENSE, and is | 9 | | available through the world-wide-web at the following url: | 10 | | http://www.php.net/license/3_01.txt | 11 | | If you did not receive a copy of the PHP license and are unable to | 12 | | obtain it through the world-wide-web, please send a note to | 13 | | license@php.net so we can mail you a copy immediately. | 14 | +----------------------------------------------------------------------+ 15 | | Authors: Derick Rethans | 16 | | Michael Maclean | 17 | | Nathaniel McHugh | 18 | | Marcus Deglos | 19 | +----------------------------------------------------------------------+ 20 | */ 21 | #ifndef PHP_GEO_ARRAY_H 22 | #define PHP_GEO_ARRAY_H 23 | 24 | typedef struct geo_array { 25 | double *x; 26 | double *y; 27 | char *status; 28 | int count; 29 | int allocated; 30 | } geo_array; 31 | 32 | geo_array *geo_array_ctor(int element_count); 33 | void geo_array_add(geo_array *points, double lat, double lon); 34 | void geo_array_dtor(geo_array *points); 35 | #endif /* PHP_GEO_ARRAY_H */ 36 | -------------------------------------------------------------------------------- /geo_lat_long.h: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | PHP Version 7 | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) 2017 The PHP Group | 6 | +----------------------------------------------------------------------+ 7 | | This source file is subject to version 3.01 of the PHP license, | 8 | | that is bundled with this package in the file LICENSE, and is | 9 | | available through the world-wide-web at the following url: | 10 | | http://www.php.net/license/3_01.txt | 11 | | If you did not receive a copy of the PHP license and are unable to | 12 | | obtain it through the world-wide-web, please send a note to | 13 | | license@php.net so we can mail you a copy immediately. | 14 | +----------------------------------------------------------------------+ 15 | | Authors: Emir Beganovic | 16 | +----------------------------------------------------------------------+ 17 | */ 18 | #ifndef PHP_GEO_LAT_LONG_H 19 | #define PHP_GEO_LAT_LONG_H 20 | 21 | typedef struct { 22 | double x; 23 | double y; 24 | double z; 25 | } geo_lat_long; 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /geohash.c: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | PHP Version 7 | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) 2017 The PHP Group | 6 | +----------------------------------------------------------------------+ 7 | | This source file is subject to version 3.01 of the PHP license, | 8 | | that is bundled with this package in the file LICENSE, and is | 9 | | available through the world-wide-web at the following url: | 10 | | http://www.php.net/license/3_01.txt | 11 | | If you did not receive a copy of the PHP license and are unable to | 12 | | obtain it through the world-wide-web, please send a note to | 13 | | license@php.net so we can mail you a copy immediately. | 14 | +----------------------------------------------------------------------+ 15 | | Authors: Emir Beganovic | 16 | +----------------------------------------------------------------------+ 17 | */ 18 | 19 | #include 20 | 21 | #include "php.h" 22 | #include "geo_lat_long.h" 23 | #include "geohash.h" 24 | 25 | #define MAX_LAT 90.0 26 | #define MIN_LAT -90.0 27 | 28 | #define MAX_LONG 180.0 29 | #define MIN_LONG -180.0 30 | 31 | typedef struct interval_string { 32 | double high; 33 | double low; 34 | } interval_struct; 35 | 36 | static char char_map[32] = "0123456789bcdefghjkmnpqrstuvwxyz"; 37 | static size_t char_map_size = sizeof(char_map); 38 | 39 | char *php_geo_geohash_encode(double latitude, double longitude, int precision) 40 | { 41 | char *hash; 42 | int steps; 43 | double coord, mid; 44 | int is_even = 1; 45 | unsigned int hash_char = 0; 46 | int i; 47 | interval_struct lat_interval = { MAX_LAT, MIN_LAT }; 48 | interval_struct lng_interval = { MAX_LONG, MIN_LONG }; 49 | interval_struct *interval; 50 | 51 | if (precision < 0) { 52 | precision = 0; 53 | } 54 | 55 | hash = (char*)safe_emalloc(precision, sizeof(char), 1); 56 | 57 | hash[precision] = '\0'; 58 | steps = precision * 5.0; 59 | 60 | for (i = 1; i <= steps; i++) { 61 | if (is_even) { 62 | interval = &lng_interval; 63 | coord = longitude; 64 | } else { 65 | interval = &lat_interval; 66 | coord = latitude; 67 | } 68 | 69 | mid = (interval->low + interval->high) / 2.0; 70 | hash_char = hash_char << 1; 71 | 72 | if (coord > mid) { 73 | interval->low = mid; 74 | hash_char |= 0x01; 75 | } else { 76 | interval->high = mid; 77 | } 78 | 79 | if (!(i % 5)) { 80 | hash[(i - 1) / 5] = char_map[hash_char]; 81 | hash_char = 0; 82 | } 83 | 84 | is_even = !is_even; 85 | } 86 | 87 | return hash; 88 | } 89 | 90 | static unsigned int index_for_char(char c, char *string, size_t string_amount) 91 | { 92 | unsigned int index = -1; 93 | int i; 94 | 95 | for (i = 0; i < string_amount; i++) { 96 | if (c == string[i]) { 97 | index = i; 98 | break; 99 | } 100 | } 101 | 102 | return index; 103 | } 104 | 105 | geo_lat_long php_geo_geohash_decode(char *hash, size_t char_amount) 106 | { 107 | geo_lat_long coordinate; 108 | 109 | if (char_amount) { 110 | int charmap_index; 111 | double delta; 112 | int i, j; 113 | interval_struct lat_interval = { MAX_LAT, MIN_LAT }; 114 | interval_struct lng_interval = { MAX_LONG, MIN_LONG }; 115 | interval_struct *interval; 116 | 117 | int is_even = 1; 118 | 119 | for (i = 0; i < char_amount; i++) { 120 | charmap_index = index_for_char(hash[i], char_map, char_map_size); 121 | 122 | /* Interpret the last 5 bits of the integer */ 123 | for (j = 0; j < 5; j++) { 124 | interval = is_even ? &lng_interval : &lat_interval; 125 | 126 | delta = (interval->high - interval->low) / 2.0; 127 | 128 | if ((charmap_index << j) & 0x0010) { 129 | interval->low += delta; 130 | } else { 131 | interval->high -= delta; 132 | } 133 | 134 | is_even = !is_even; 135 | } 136 | } 137 | 138 | coordinate.x = lat_interval.high - ((lat_interval.high - lat_interval.low) / 2.0); 139 | coordinate.y = lng_interval.high - ((lng_interval.high - lng_interval.low) / 2.0); 140 | coordinate.z = 0; 141 | } 142 | 143 | return coordinate; 144 | } 145 | -------------------------------------------------------------------------------- /geohash.h: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | PHP Version 5/7 | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) 2017 The PHP Group | 6 | +----------------------------------------------------------------------+ 7 | | This source file is subject to version 3.01 of the PHP license, | 8 | | that is bundled with this package in the file LICENSE, and is | 9 | | available through the world-wide-web at the following url: | 10 | | http://www.php.net/license/3_01.txt | 11 | | If you did not receive a copy of the PHP license and are unable to | 12 | | obtain it through the world-wide-web, please send a note to | 13 | | license@php.net so we can mail you a copy immediately. | 14 | +----------------------------------------------------------------------+ 15 | | Authors: Emir Beganovic | 16 | +----------------------------------------------------------------------+ 17 | */ 18 | #ifndef PHP_GEOHASH_H 19 | #define PHP_GEOHASH_H 20 | 21 | char *php_geo_geohash_encode(double lat, double lng, int precision); 22 | geo_lat_long php_geo_geohash_decode(char *hash, size_t hash_len); 23 | 24 | #endif /* PHP_GEOHASH_H */ 25 | -------------------------------------------------------------------------------- /geospatial.c: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | PHP | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) 1997-2024 The PHP Group | 6 | +----------------------------------------------------------------------+ 7 | | This source file is subject to version 3.01 of the PHP license, | 8 | | that is bundled with this package in the file LICENSE, and is | 9 | | available through the world-wide-web at the following url: | 10 | | http://www.php.net/license/3_01.txt | 11 | | If you did not receive a copy of the PHP license and are unable to | 12 | | obtain it through the world-wide-web, please send a note to | 13 | | license@php.net so we can mail you a copy immediately. | 14 | +----------------------------------------------------------------------+ 15 | | Authors: Derick Rethans | 16 | | Michael Maclean | 17 | | Nathaniel McHugh | 18 | | Marcus Deglos | 19 | | Emir Beganovic | 20 | +----------------------------------------------------------------------+ 21 | */ 22 | 23 | #ifdef HAVE_CONFIG_H 24 | #include "config.h" 25 | #endif 26 | 27 | #include "php.h" 28 | #include "php_ini.h" 29 | #include "ext/standard/info.h" 30 | 31 | #include "php_geospatial.h" 32 | #include "php_geospatial_arginfo.h" 33 | 34 | #include "geo_array.h" 35 | #include "geo_lat_long.h" 36 | #include "geohash.h" 37 | #include "Zend/zend_exceptions.h" 38 | #include "ext/spl/spl_exceptions.h" 39 | 40 | /** 41 | * The WGS84 elipsoid semi major axes 42 | */ 43 | const geo_ellipsoid wgs84 = {6378137.000, 6356752.3142, 1.0/298.257223563}; 44 | 45 | /** 46 | * The Airy 1830 elipsoid semi major axes 47 | */ 48 | const geo_ellipsoid airy_1830 = {6377563.396, 6356256.910, 1.0/299.3249646}; 49 | /** 50 | * The GRS 80 elipsoid semi major axes 51 | */ 52 | const geo_ellipsoid grs80 = {6378137.000, 6356752.314140, 1.0/298.257222101}; 53 | 54 | /** 55 | * The values of the 7 variables for performing helmert transformation between 56 | * wgs84 and osgb36 57 | */ 58 | const geo_helmert_constants wgs84_osgb36 = { 59 | -446.448, 60 | 125.157, 61 | -542.060, 62 | 0.0000204894, 63 | -0.1502, 64 | -0.2470, 65 | -0.8421 66 | }; 67 | 68 | /** 69 | * The values of the 7 variables for performing helmert transformation between 70 | * osgb36 and wgs84 -1 * the values for the reverse transformation 71 | */ 72 | const geo_helmert_constants osgb36_wgs84 = { 73 | 446.448, 74 | -125.157, 75 | 542.060, 76 | -0.0000204894, 77 | 0.1502, 78 | 0.2470, 79 | 0.8421 80 | }; 81 | 82 | /* {{{ geospatial_module_entry 83 | */ 84 | zend_module_entry geospatial_module_entry = { 85 | #if ZEND_MODULE_API_NO >= 20010901 86 | STANDARD_MODULE_HEADER, 87 | #endif 88 | "geospatial", 89 | ext_functions, 90 | PHP_MINIT(geospatial), 91 | NULL, 92 | NULL, 93 | NULL, 94 | PHP_MINFO(geospatial), 95 | #if ZEND_MODULE_API_NO >= 20010901 96 | PHP_GEOSPATIAL_VERSION, 97 | #endif 98 | STANDARD_MODULE_PROPERTIES 99 | }; 100 | /* }}} */ 101 | 102 | #ifdef COMPILE_DL_GEOSPATIAL 103 | ZEND_GET_MODULE(geospatial) 104 | #endif 105 | 106 | /* {{{ PHP_MINIT_FUNCTION 107 | */ 108 | PHP_MINIT_FUNCTION(geospatial) 109 | { 110 | REGISTER_DOUBLE_CONSTANT("GEO_DEG_TO_RAD", GEO_DEG_TO_RAD, CONST_CS | CONST_PERSISTENT); 111 | REGISTER_DOUBLE_CONSTANT("GEO_EARTH_RADIUS", GEO_EARTH_RADIUS, CONST_CS | CONST_PERSISTENT); 112 | REGISTER_LONG_CONSTANT("GEO_AIRY_1830", GEO_AIRY_1830, CONST_CS | CONST_PERSISTENT); 113 | REGISTER_LONG_CONSTANT("GEO_WGS84", GEO_WGS84, CONST_CS | CONST_PERSISTENT); 114 | return SUCCESS; 115 | } 116 | /* }}} */ 117 | 118 | /* {{{ PHP_MSHUTDOWN_FUNCTION 119 | */ 120 | PHP_MSHUTDOWN_FUNCTION(geospatial) 121 | { 122 | return SUCCESS; 123 | } 124 | /* }}} */ 125 | 126 | /* {{{ PHP_MINFO_FUNCTION 127 | */ 128 | PHP_MINFO_FUNCTION(geospatial) 129 | { 130 | php_info_print_table_start(); 131 | php_info_print_table_header(2, "Geospatial functions", "enabled"); 132 | php_info_print_table_row(2, "Version", PHP_GEOSPATIAL_VERSION); 133 | php_info_print_table_end(); 134 | } 135 | /* }}} */ 136 | 137 | /* {{{ Version compat helpers */ 138 | #if PHP_VERSION_ID >= 70000 139 | # define ADD_STRING(zv, name, val) add_assoc_string_ex(zv, name, sizeof(name)-1, val); 140 | # define GEOSPAT_MAKE_STD_ZVAL(zv) zv = ecalloc(sizeof(zval), 1); 141 | #else 142 | # define ADD_STRING(zv, name, val) add_assoc_string_ex(zv, name, sizeof(name), val, 1); 143 | # define GEOSPAT_MAKE_STD_ZVAL(zv) MAKE_STD_ZVAL(zv) 144 | #endif 145 | /* }}} */ 146 | 147 | /* {{{ Helpers */ 148 | void retval_point_from_coordinates(zval *return_value, double lon, double lat) 149 | { 150 | zval *coordinates; 151 | 152 | array_init(return_value); 153 | GEOSPAT_MAKE_STD_ZVAL(coordinates); 154 | array_init(coordinates); 155 | ADD_STRING(return_value, "type", "Point"); 156 | add_next_index_double(coordinates, lon); 157 | add_next_index_double(coordinates, lat); 158 | #if PHP_VERSION_ID >= 70000 159 | add_assoc_zval_ex(return_value, "coordinates", sizeof("coordinates") - 1, coordinates); 160 | efree(coordinates); 161 | #else 162 | add_assoc_zval_ex(return_value, "coordinates", sizeof("coordinates"), coordinates); 163 | #endif 164 | } 165 | 166 | static int parse_point_pair(zval *coordinates, double *lon, double *lat) 167 | { 168 | HashTable *coords; 169 | #if PHP_VERSION_ID >= 70000 170 | zval *z_lon, *z_lat; 171 | #else 172 | zval **z_lon, **z_lat; 173 | #endif 174 | 175 | if (Z_TYPE_P(coordinates) != IS_ARRAY) { 176 | zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0, "Expected a coordinate pair as an array, but %s given", zend_zval_type_name(coordinates)); 177 | return 0; 178 | } 179 | 180 | coords = HASH_OF(coordinates); 181 | if (coords->nNumOfElements != 2) { 182 | return 0; 183 | } 184 | #if PHP_VERSION_ID >= 70000 185 | if ((z_lon = zend_hash_index_find(coords, 0)) == NULL) { 186 | return 0; 187 | } 188 | if ((z_lat = zend_hash_index_find(coords, 1)) == NULL) { 189 | return 0; 190 | } 191 | convert_to_double_ex(z_lon); 192 | convert_to_double_ex(z_lat); 193 | *lon = Z_DVAL_P(z_lon); 194 | *lat = Z_DVAL_P(z_lat); 195 | #else 196 | if (zend_hash_index_find(coords, 0, (void**) &z_lon) != SUCCESS) { 197 | return 0; 198 | } 199 | if (zend_hash_index_find(coords, 1, (void**) &z_lat) != SUCCESS) { 200 | return 0; 201 | } 202 | convert_to_double_ex(z_lon); 203 | convert_to_double_ex(z_lat); 204 | *lon = Z_DVAL_PP(z_lon); 205 | *lat = Z_DVAL_PP(z_lat); 206 | #endif 207 | return 1; 208 | } 209 | 210 | int geojson_point_to_lon_lat(zval *point, double *lon, double *lat) 211 | { 212 | #if PHP_VERSION_ID >= 70000 213 | zval *type, *coordinates; 214 | 215 | if ((type = zend_hash_str_find(HASH_OF(point), "type", sizeof("type") - 1)) == NULL) { 216 | return 0; 217 | } 218 | if (Z_TYPE_P(type) != IS_STRING || strcmp(Z_STRVAL_P(type), "Point") != 0) { 219 | return 0; 220 | } 221 | if ((coordinates = zend_hash_str_find(HASH_OF(point), "coordinates", sizeof("coordinates") - 1)) == NULL) { 222 | return 0; 223 | } 224 | if (Z_TYPE_P(coordinates) != IS_ARRAY) { 225 | return 0; 226 | } 227 | return parse_point_pair(coordinates, lon, lat); 228 | #else 229 | zval **type, **coordinates; 230 | 231 | if (zend_hash_find(HASH_OF(point), "type", sizeof("type"), (void**) &type) != SUCCESS) { 232 | return 0; 233 | } 234 | if (Z_TYPE_PP(type) != IS_STRING || strcmp(Z_STRVAL_PP(type), "Point") != 0) { 235 | return 0; 236 | } 237 | if (zend_hash_find(HASH_OF(point), "coordinates", sizeof("coordinates"), (void**) &coordinates) != SUCCESS) { 238 | return 0; 239 | } 240 | if (Z_TYPE_PP(coordinates) != IS_ARRAY) { 241 | return 0; 242 | } 243 | return parse_point_pair(*coordinates, lon, lat); 244 | #endif 245 | } 246 | 247 | /* }}} */ 248 | 249 | double php_geo_haversine(double from_lat, double from_long, double to_lat, double to_long) 250 | { 251 | double delta_lat, delta_long; 252 | double latH, longH, result; 253 | 254 | delta_lat = (from_lat - to_lat); 255 | delta_long = (from_long - to_long); 256 | 257 | latH = sin(delta_lat * 0.5); 258 | latH *= latH; 259 | longH = sin(delta_long * 0.5); 260 | longH *= longH; 261 | 262 | result = cos(from_lat) * cos(to_lat); 263 | result = 2.0 * asin(sqrt(latH + result * longH)); 264 | 265 | return result; 266 | } 267 | 268 | 269 | geo_ellipsoid get_ellipsoid(long ellipsoid_const) 270 | { 271 | switch (ellipsoid_const) { 272 | case GEO_AIRY_1830: 273 | return airy_1830; 274 | case GEO_WGS84: 275 | default: 276 | return wgs84; 277 | } 278 | } 279 | 280 | double php_geo_vincenty(double from_lat, double from_long, double to_lat, double to_long, geo_ellipsoid eli) 281 | { 282 | double U1, U2, L, lambda, lambdaP; 283 | double sinSigma, cosSigma, sigma, sinLambda, cosLambda; 284 | double sinU1, sinU2, cosU1, cosU2; 285 | double sinAlpha, cos2Alpha; 286 | double cosof2sigma, A, B, C, uSq, deltaSigma, s; 287 | int loopLimit = 100; 288 | double precision = 0.000000000001; 289 | 290 | U1 = atan((1.0 - eli.f) * tan(from_lat)); 291 | U2 = atan((1.0 - eli.f) * tan(to_lat)); 292 | L = to_long - from_long; 293 | sinU1 = sin(U1); 294 | cosU1 = cos(U1); 295 | sinU2 = sin(U2); 296 | cosU2 = cos(U2); 297 | lambda = L; 298 | do { 299 | sinLambda = sin(lambda); 300 | cosLambda = cos(lambda); 301 | sinSigma = sqrt((cosU2*sinLambda) * (cosU2*sinLambda) + 302 | (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda) * (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda)); 303 | cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda; 304 | sigma = atan2(sinSigma, cosSigma); 305 | sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma; 306 | cos2Alpha = 1.0 - sinAlpha * sinAlpha; 307 | cosof2sigma = cosSigma - 2.0 * sinU1 * sinU2 / cos2Alpha; 308 | C = eli.f / 16.0 * cos2Alpha * (4.0 + eli.f * (4.0 - 3.0 * cos2Alpha)); 309 | lambdaP = lambda; 310 | lambda = L + (1.0 - C) * eli.f * sinAlpha * 311 | (sigma + C*sinSigma*(cosof2sigma+C*cosSigma*(-1.0 + 2.0 *cosof2sigma*cosof2sigma))); 312 | --loopLimit; 313 | } while (fabs(lambda - lambdaP) > precision && loopLimit > 0); 314 | uSq = cos2Alpha * (eli.a * eli.a - eli.b * eli.b) / (eli.b * eli.b); 315 | A = 1.0 + uSq / 16384.0 * (4096.0 + uSq * (-768.0 + uSq * (320.0 - 175.0 * uSq))); 316 | B = uSq / 1024.0 * ( 256.0 + uSq * (-128.0 + uSq * (74.0 - 47.0 * uSq))); 317 | deltaSigma = B * sinSigma * (cosof2sigma+B/4.0 * (cosSigma * (-1.0 + 2.0 *cosof2sigma*cosof2sigma) - 318 | B / 6.0 * cosof2sigma * (-3.0 + 4.0 *sinSigma*sinSigma) * (-3.0 + 4.0 *cosof2sigma*cosof2sigma))); 319 | s = eli.b * A * (sigma - deltaSigma); 320 | s = floor(s * 1000) / 1000; 321 | return s; 322 | } 323 | 324 | geo_helmert_constants get_helmert_constants(long from, long to) 325 | { 326 | switch (from - to) { 327 | case 1: 328 | return osgb36_wgs84; 329 | default: 330 | case -1: 331 | return wgs84_osgb36; 332 | } 333 | } 334 | 335 | geo_cartesian helmert(double x, double y, double z, geo_helmert_constants helmet_consts) 336 | { 337 | double rX, rY, rZ; 338 | double xOut, yOut, zOut; 339 | double scale_change; 340 | geo_cartesian point; 341 | scale_change = 1 + (helmet_consts.scale_change); 342 | rX = helmet_consts.rotation_x / GEO_SEC_IN_DEG * GEO_DEG_TO_RAD; 343 | rY = helmet_consts.rotation_y / GEO_SEC_IN_DEG * GEO_DEG_TO_RAD; 344 | rZ = helmet_consts.rotation_z / GEO_SEC_IN_DEG * GEO_DEG_TO_RAD; 345 | 346 | xOut = helmet_consts.translation_x + ((x - (rZ * y) + (rY * z)) * scale_change); 347 | 348 | yOut = helmet_consts.translation_y + (((rZ * x) + y - (rX * z)) * scale_change); 349 | 350 | zOut = helmet_consts.translation_z + (((-1 * rY * x) + (rX * y) + z) * scale_change); 351 | 352 | point.x = xOut; 353 | point.y = yOut; 354 | point.z = zOut; 355 | return point; 356 | } 357 | 358 | geo_cartesian polar_to_cartesian(double latitude, double longitude, geo_ellipsoid eli) 359 | { 360 | double x, y, z; 361 | 362 | geo_cartesian point; 363 | double phi = latitude * GEO_DEG_TO_RAD; 364 | double lambda = longitude * GEO_DEG_TO_RAD; 365 | double eSq = ((eli.a * eli.a) - (eli.b * eli.b)) / (eli.a * eli.a); 366 | double nu = eli.a / sqrt(1 - (eSq * sin(phi) * sin(phi))); 367 | x = nu + HEIGHT; 368 | x *= cos(phi) * cos(lambda); 369 | y = nu + HEIGHT; 370 | y *= cos(phi) * sin(lambda); 371 | z = ((1 - eSq) * nu) + HEIGHT; 372 | z*= sin(phi); 373 | point.x = x; 374 | point.y = y; 375 | point.z = z; 376 | return point; 377 | } 378 | 379 | 380 | geo_lat_long cartesian_to_polar(double x, double y, double z, geo_ellipsoid eli) 381 | { 382 | 383 | double nu, lambda, h; 384 | geo_lat_long polar; 385 | 386 | /* aiming for 1m accuracy */ 387 | double precision = 0.1 / eli.a; 388 | double eSq = ((eli.a * eli.a) - (eli.b * eli.b)) / (eli.a * eli.a); 389 | double p = sqrt(x * x + y * y); 390 | double phi = atan2(z, p * (1 - eSq)); 391 | double phiP = 2 * M_PI; 392 | 393 | while (abs(phi - phiP) > precision) { 394 | nu = eli.a / sqrt(1 - eSq * sin(phi) * sin(phi)); 395 | phiP = phi; 396 | phi = atan2(z + eSq * nu * sin(phi), p); 397 | } 398 | 399 | lambda = atan2(y ,x); 400 | h = p / cos(phi) - nu; 401 | polar.x = phi / GEO_DEG_TO_RAD; 402 | polar.y = lambda / GEO_DEG_TO_RAD; 403 | polar.z = h; 404 | 405 | return polar; 406 | } 407 | 408 | /* {{{ proto double dms_to_decimal(double degrees, double minutes, double seconds [,string direction]) 409 | * Convert degrees, minutes & seconds values to decimal degrees */ 410 | PHP_FUNCTION(dms_to_decimal) 411 | { 412 | double degrees, minutes, sign; 413 | double seconds, decimal; 414 | char *direction = ""; 415 | #if PHP_VERSION_ID >= 70000 416 | size_t direction_len; 417 | #else 418 | int direction_len; 419 | #endif 420 | 421 | if (zend_parse_parameters(ZEND_NUM_ARGS(), "ddd|s", °rees, &minutes, &seconds, &direction, &direction_len) == FAILURE) { 422 | return; 423 | } 424 | 425 | if (strcmp("", direction) == 0) { 426 | sign = degrees > 1 ? 1 : -1; 427 | } else { 428 | sign = strcmp(direction, "S") == 0 || strcmp(direction, "W") == 0 ? -1 : 1; 429 | } 430 | 431 | decimal = fabs(degrees) + minutes / 60 + seconds / 3600; 432 | decimal *= sign; 433 | RETURN_DOUBLE(decimal); 434 | } 435 | /* }}} */ 436 | 437 | /* {{{ proto array decimal_to_dms(double decimal, string coordinate) 438 | * Convert decimal degrees value to whole degrees and minutes and decimal seconds */ 439 | PHP_FUNCTION(decimal_to_dms) 440 | { 441 | double decimal, seconds; 442 | int degrees, minutes; 443 | char *direction; 444 | char *coordinate; 445 | #if PHP_VERSION_ID >= 70000 446 | size_t coordinate_len; 447 | #else 448 | int coordinate_len; 449 | #endif 450 | 451 | if (zend_parse_parameters(ZEND_NUM_ARGS(), "ds", &decimal, &coordinate, &coordinate_len) == FAILURE) { 452 | return; 453 | } 454 | 455 | if (strcmp(coordinate, "longitude") == 0) { 456 | direction = decimal < 1 ? "W" : "E"; 457 | } else { 458 | direction = decimal < 1 ? "S" : "N"; 459 | } 460 | 461 | array_init(return_value); 462 | decimal = fabs(decimal); 463 | degrees = (int) decimal; 464 | minutes = decimal * 60 - degrees * 60; 465 | seconds = decimal * 3600 - degrees * 3600 - minutes * 60; 466 | add_assoc_long(return_value, "degrees", degrees); 467 | add_assoc_long(return_value, "minutes", minutes); 468 | add_assoc_double(return_value, "seconds", seconds); 469 | ADD_STRING(return_value, "direction", direction); 470 | } 471 | /* }}} */ 472 | 473 | /* {{{ proto array helmert(double x, double y, double z [, long from_reference_ellipsoid, long to_reference_ellipsoid]) 474 | * Convert cartesian co-ordinates between reference elipsoids */ 475 | PHP_FUNCTION(helmert) 476 | { 477 | double x, y, z; 478 | geo_cartesian point; 479 | long from_reference_ellipsoid = 0, to_reference_ellipsoid = 0; 480 | geo_helmert_constants helmert_constants; 481 | 482 | if (zend_parse_parameters(ZEND_NUM_ARGS(), "ddd|ll", &x, &y, &z, &from_reference_ellipsoid, &to_reference_ellipsoid) == FAILURE) { 483 | return; 484 | } 485 | 486 | array_init(return_value); 487 | helmert_constants = get_helmert_constants(from_reference_ellipsoid, to_reference_ellipsoid); 488 | point = helmert(x, y, z, helmert_constants); 489 | add_assoc_double(return_value, "x", point.x); 490 | add_assoc_double(return_value, "y", point.y); 491 | add_assoc_double(return_value, "z", point.z); 492 | } 493 | /* }}} */ 494 | 495 | /* {{{ proto array polar_to_cartesian(double latitude, double longitude[, long reference_ellipsoid]) 496 | * Convert polar ones (latitude, longitude) tp cartesian co-ordiantes (x, y, z) */ 497 | PHP_FUNCTION(polar_to_cartesian) 498 | { 499 | double latitude, longitude; 500 | long reference_ellipsoid = 0; 501 | geo_cartesian point; 502 | 503 | if (zend_parse_parameters(ZEND_NUM_ARGS(), "dd|l", &latitude, &longitude, &reference_ellipsoid) == FAILURE) { 504 | return; 505 | } 506 | 507 | geo_ellipsoid eli = get_ellipsoid(reference_ellipsoid); 508 | array_init(return_value); 509 | point = polar_to_cartesian(latitude, longitude, eli); 510 | add_assoc_double(return_value, "x", point.x); 511 | add_assoc_double(return_value, "y", point.y); 512 | add_assoc_double(return_value, "z", point.z); 513 | } 514 | /* }}} */ 515 | 516 | /* {{{ proto array cartesian_to_polar(double x, double y, double z [, long reference_ellipsoid]) 517 | * Convert cartesian co-ordinates (x, y, z) to polar ones (latitude, longitude) */ 518 | PHP_FUNCTION(cartesian_to_polar) 519 | { 520 | double x, y, z; 521 | long reference_ellipsoid = 0; 522 | geo_lat_long polar; 523 | 524 | if (zend_parse_parameters(ZEND_NUM_ARGS(), "ddd|l", &x, &y, &z, &reference_ellipsoid) == FAILURE) { 525 | return; 526 | } 527 | 528 | geo_ellipsoid eli = get_ellipsoid(reference_ellipsoid); 529 | array_init(return_value); 530 | polar = cartesian_to_polar(x, y, z, eli); 531 | add_assoc_double(return_value, "lat", polar.x); 532 | add_assoc_double(return_value, "long", polar.y); 533 | add_assoc_double(return_value, "height", polar.z); 534 | } 535 | /* }}} */ 536 | 537 | 538 | /* {{{ proto GeoJSONPoint transform_datum(GeoJSONPoint coordinates, long from_reference_ellipsoid, long to_reference_ellipsoid) 539 | * Unified function to transform projection of geo-coordinates between datums */ 540 | PHP_FUNCTION(transform_datum) 541 | { 542 | double latitude, longitude; 543 | zval *geojson; 544 | long from_reference_ellipsoid, to_reference_ellipsoid; 545 | geo_cartesian point, converted_point; 546 | geo_lat_long polar; 547 | geo_helmert_constants helmert_constants; 548 | 549 | if (zend_parse_parameters(ZEND_NUM_ARGS(), "all", &geojson, &from_reference_ellipsoid, &to_reference_ellipsoid) == FAILURE) { 550 | return; 551 | } 552 | 553 | if (!geojson_point_to_lon_lat(geojson, &longitude, &latitude)) { 554 | RETURN_FALSE; 555 | } 556 | 557 | geo_ellipsoid eli_from = get_ellipsoid(from_reference_ellipsoid); 558 | geo_ellipsoid eli_to = get_ellipsoid(to_reference_ellipsoid); 559 | point = polar_to_cartesian(latitude, longitude, eli_from); 560 | helmert_constants = get_helmert_constants(from_reference_ellipsoid, to_reference_ellipsoid); 561 | converted_point = helmert(point.x, point.y, point.z, helmert_constants); 562 | polar = cartesian_to_polar(converted_point.x, converted_point.y, converted_point.z, eli_to); 563 | /* 564 | array_init(return_value); 565 | add_assoc_double(return_value, "lat", polar.latitude); 566 | add_assoc_double(return_value, "long", polar.longitude); 567 | add_assoc_double(return_value, "height", polar.height); 568 | */ 569 | retval_point_from_coordinates(return_value, polar.y, polar.x); 570 | } 571 | /* }}} */ 572 | 573 | /* {{{ proto double haversine(GeoJSONPoint from, GeoJSONPoint to [, double radius ]) 574 | * Calculates the greater circle distance between two points in m */ 575 | PHP_FUNCTION(haversine) 576 | { 577 | double radius = GEO_EARTH_RADIUS; 578 | zval *from_geojson, *to_geojson; 579 | double from_lat, from_long, to_lat, to_long; 580 | 581 | if (zend_parse_parameters(ZEND_NUM_ARGS(), "aa|d", &from_geojson, &to_geojson, &radius) == FAILURE) { 582 | return; 583 | } 584 | 585 | geojson_point_to_lon_lat(from_geojson, &from_long, &from_lat); 586 | geojson_point_to_lon_lat(to_geojson, &to_long, &to_lat); 587 | 588 | RETURN_DOUBLE(php_geo_haversine(from_lat * GEO_DEG_TO_RAD, from_long * GEO_DEG_TO_RAD, to_lat * GEO_DEG_TO_RAD, to_long * GEO_DEG_TO_RAD) * radius); 589 | } 590 | /* }}} */ 591 | 592 | /* {{{ proto double vincenty(GeoJSONPoint from, GeoJSONPoint to [, long reference_ellipsoid ]) 593 | * Calculates the distance between two points in m */ 594 | PHP_FUNCTION(vincenty) 595 | { 596 | zval *from_geojson, *to_geojson; 597 | double from_lat, from_long, to_lat, to_long; 598 | long reference_ellipsoid = GEO_WGS84; 599 | 600 | if (zend_parse_parameters(ZEND_NUM_ARGS(), "aa|l", &from_geojson, &to_geojson, &reference_ellipsoid) == FAILURE) { 601 | return; 602 | } 603 | 604 | geojson_point_to_lon_lat(from_geojson, &from_long, &from_lat); 605 | geojson_point_to_lon_lat(to_geojson, &to_long, &to_lat); 606 | 607 | geo_ellipsoid eli = get_ellipsoid(reference_ellipsoid); 608 | RETURN_DOUBLE(php_geo_vincenty(from_lat * GEO_DEG_TO_RAD, from_long * GEO_DEG_TO_RAD, to_lat * GEO_DEG_TO_RAD, to_long * GEO_DEG_TO_RAD, eli)); 609 | } 610 | /* }}} */ 611 | 612 | void php_geo_fraction_along_gc_line(double from_lat, double from_long, double to_lat, double to_long, double fraction, double *res_lat, double *res_long) 613 | { 614 | double distance; 615 | double a, b, x, y, z; 616 | 617 | /* First we calculate the distance */ 618 | distance = php_geo_haversine(from_lat, from_long, to_lat, to_long); 619 | 620 | a = sin((1 - fraction) * distance) / sin(distance); 621 | b = sin(fraction * distance) / sin(distance); 622 | x = a * cos(from_lat) * cos(from_long) + b * cos(to_lat) * cos(to_long); 623 | y = a * cos(from_lat) * sin(from_long) + b * cos(to_lat) * sin(to_long); 624 | z = a * sin(from_lat) + b * sin(to_lat); 625 | 626 | *res_lat = atan2(z, sqrt(x * x + y * y)); 627 | *res_long = atan2(y, x); 628 | } 629 | 630 | /* {{{ proto GeoJSONPoint fraction_along_gc_line(GeoJSONPoint from, GeoJSONPoint to, double fraction) 631 | * Calculates a lat/long pair at a fraction (0-1) of the distance along a GC line */ 632 | PHP_FUNCTION(fraction_along_gc_line) 633 | { 634 | zval *from_geojson, *to_geojson; 635 | double from_lat, from_long, to_lat, to_long, fraction; 636 | double res_lat, res_long; 637 | 638 | if (zend_parse_parameters(ZEND_NUM_ARGS(), "aad", &from_geojson, &to_geojson, &fraction) == FAILURE) { 639 | return; 640 | } 641 | 642 | geojson_point_to_lon_lat(from_geojson, &from_long, &from_lat); 643 | geojson_point_to_lon_lat(to_geojson, &to_long, &to_lat); 644 | 645 | php_geo_fraction_along_gc_line( 646 | from_lat * GEO_DEG_TO_RAD, 647 | from_long * GEO_DEG_TO_RAD, 648 | to_lat * GEO_DEG_TO_RAD, 649 | to_long * GEO_DEG_TO_RAD, 650 | fraction, 651 | &res_lat, &res_long 652 | ); 653 | 654 | retval_point_from_coordinates(return_value, res_long / GEO_DEG_TO_RAD, res_lat / GEO_DEG_TO_RAD); 655 | } 656 | /* }}} */ 657 | 658 | double php_initial_bearing(double from_lat, double from_long, double to_lat, double to_long) 659 | { 660 | /* 661 | var y = Math.sin(dLon) * Math.cos(lat2); 662 | var x = Math.cos(lat1)*Math.sin(lat2) - 663 | Math.sin(lat1)*Math.cos(lat2)*Math.cos(dLon); 664 | var brng = Math.atan2(y, x).toDeg(); 665 | */ 666 | double x, y, initial_bearing; 667 | 668 | y = sin(to_long - from_long) * cos(to_lat); 669 | x = (cos(from_lat) * sin(to_lat)) - (sin(from_lat) * cos(to_lat) * cos(to_long - from_long)); 670 | 671 | initial_bearing = atan2(y, x); 672 | if (initial_bearing < 0) { 673 | initial_bearing += (M_PI * 2); 674 | } 675 | 676 | return initial_bearing; 677 | } 678 | 679 | /* {{{ proto float initial_bearing(GeoJSONPoint from, GeoJSONPoint to) 680 | Calculates the initial bearing to from from to to. */ 681 | PHP_FUNCTION(initial_bearing) 682 | { 683 | zval *from_geojson, *to_geojson; 684 | double from_lat, from_long, to_lat, to_long, bearing; 685 | 686 | if (zend_parse_parameters(ZEND_NUM_ARGS(), "aa", &from_geojson, &to_geojson) == FAILURE) { 687 | return; 688 | } 689 | 690 | geojson_point_to_lon_lat(from_geojson, &from_long, &from_lat); 691 | geojson_point_to_lon_lat(to_geojson, &to_long, &to_lat); 692 | 693 | bearing = php_initial_bearing( 694 | from_lat * GEO_DEG_TO_RAD, 695 | from_long * GEO_DEG_TO_RAD, 696 | to_lat * GEO_DEG_TO_RAD, 697 | to_long * GEO_DEG_TO_RAD 698 | ); 699 | 700 | RETURN_DOUBLE(bearing / GEO_DEG_TO_RAD); 701 | } 702 | /* }}} */ 703 | 704 | geo_array *geo_hashtable_to_array(zval *array) 705 | { 706 | geo_array *tmp; 707 | int element_count; 708 | HashPosition pos; 709 | #if PHP_VERSION_ID >= 70000 710 | zval *entry; 711 | #else 712 | zval **entry; 713 | #endif 714 | double lon, lat; 715 | int i = 0; 716 | 717 | element_count = zend_hash_num_elements(Z_ARRVAL_P(array)); 718 | tmp = geo_array_ctor(element_count); 719 | 720 | #if PHP_VERSION_ID >= 70000 721 | ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(array), entry) { 722 | 723 | if (!parse_point_pair(entry, &lon, &lat)) { 724 | #else 725 | zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(array), &pos); 726 | while (zend_hash_get_current_data_ex(Z_ARRVAL_P(array), (void **)&entry, &pos) == SUCCESS) { 727 | 728 | if (!parse_point_pair(*entry, &lon, &lat)) { 729 | #endif 730 | 731 | goto failure; 732 | } 733 | 734 | tmp->x[i] = lon; 735 | tmp->y[i] = lat; 736 | tmp->status[i] = 1; 737 | 738 | zend_hash_move_forward_ex(Z_ARRVAL_P(array), &pos); 739 | i++; 740 | #if PHP_VERSION_ID >= 70000 741 | } ZEND_HASH_FOREACH_END(); 742 | #else 743 | } 744 | #endif 745 | 746 | return tmp; 747 | 748 | failure: 749 | geo_array_dtor(tmp); 750 | return NULL; 751 | } 752 | 753 | int geojson_linestring_to_array(zval *line, geo_array **array) 754 | { 755 | geo_array *tmp; 756 | #if PHP_VERSION_ID >= 70000 757 | zval *type, *coordinates; 758 | 759 | if (Z_TYPE_P(line) != IS_ARRAY) { 760 | return 0; 761 | } 762 | 763 | if ((type = zend_hash_str_find(HASH_OF(line), "type", sizeof("type") - 1)) == NULL) { 764 | return 0; 765 | } 766 | if (Z_TYPE_P(type) != IS_STRING || strcmp(Z_STRVAL_P(type), "Linestring") != 0) { 767 | return 0; 768 | } 769 | if ((coordinates = zend_hash_str_find(HASH_OF(line), "coordinates", sizeof("coordinates") - 1)) == NULL) { 770 | return 0; 771 | } 772 | if (Z_TYPE_P(coordinates) != IS_ARRAY) { 773 | return 0; 774 | } 775 | 776 | tmp = geo_hashtable_to_array(coordinates); 777 | #else 778 | zval **type, **coordinates; 779 | 780 | if (Z_TYPE_P(line) != IS_ARRAY) { 781 | return 0; 782 | } 783 | 784 | if (zend_hash_find(HASH_OF(line), "type", sizeof("type"), (void**) &type) != SUCCESS) { 785 | return 0; 786 | } 787 | if (Z_TYPE_PP(type) != IS_STRING || strcmp(Z_STRVAL_PP(type), "Linestring") != 0) { 788 | return 0; 789 | } 790 | if (zend_hash_find(HASH_OF(line), "coordinates", sizeof("coordinates"), (void**) &coordinates) != SUCCESS) { 791 | return 0; 792 | } 793 | if (Z_TYPE_PP(coordinates) != IS_ARRAY) { 794 | return 0; 795 | } 796 | 797 | tmp = geo_hashtable_to_array(*coordinates); 798 | #endif 799 | if (tmp && array) { 800 | *array = tmp; 801 | return 1; 802 | } 803 | 804 | return 0; 805 | } 806 | 807 | double rdp_find_perpendicular_distable(double pX, double pY, double p1X, double p1Y, double p2X, double p2Y) 808 | { 809 | double slope, intercept, result; 810 | 811 | if (p1X == p2X) { 812 | return fabs(pX - p1X); 813 | } else { 814 | slope = (p2Y - p1Y) / (p2X - p1X); 815 | intercept = p1Y - (slope * p1X); 816 | result = fabs(slope * pX - pY + intercept) / sqrt(pow(slope, 2) + 1); 817 | return result; 818 | } 819 | } 820 | 821 | void rdp_simplify(geo_array *points, double epsilon, int start, int end) 822 | { 823 | double firstX = points->x[start]; 824 | double firstY = points->y[start]; 825 | double lastX = points->x[end]; 826 | double lastY = points->y[end]; 827 | int index = -1; 828 | double dist = 0.0, current_dist; 829 | int i; 830 | 831 | if (end - start < 2) { 832 | return; 833 | } 834 | 835 | for (i = start + 1; i < end; i++) { 836 | if (!points->status[i]) { 837 | continue; 838 | } 839 | 840 | current_dist = rdp_find_perpendicular_distable(points->x[i], points->y[i], firstX, firstY, lastX, lastY); 841 | 842 | if (current_dist > dist) { 843 | dist = current_dist; 844 | index = i; 845 | } 846 | } 847 | 848 | if (dist > epsilon) { 849 | rdp_simplify(points, epsilon, start, index); 850 | rdp_simplify(points, epsilon, index, end); 851 | 852 | return; 853 | } else { 854 | for (i = start + 1; i < end; i++) { 855 | points->status[i] = 0; 856 | } 857 | return; 858 | } 859 | } 860 | 861 | /* {{{ proto array rdp_simplify(array points, float epsilon) 862 | Simplifies a 2D dimensional line according to the Ramer-Douglas-Peucker algorithm */ 863 | PHP_FUNCTION(rdp_simplify) 864 | { 865 | zval *points_array; 866 | double epsilon; 867 | geo_array *points; 868 | int i; 869 | zval *pair; 870 | 871 | ZEND_PARSE_PARAMETERS_START(2, 2) 872 | Z_PARAM_ARRAY(points_array) 873 | Z_PARAM_DOUBLE(epsilon) 874 | ZEND_PARSE_PARAMETERS_END(); 875 | 876 | array_init(return_value); 877 | 878 | points = geo_hashtable_to_array(points_array); 879 | if (!points) { 880 | return; 881 | } 882 | rdp_simplify(points, epsilon, 0, points->count - 1); 883 | for (i = 0; i < points->count; i++) { 884 | if (points->status[i]) { 885 | GEOSPAT_MAKE_STD_ZVAL(pair); 886 | array_init(pair); 887 | add_next_index_double(pair, points->x[i]); 888 | add_next_index_double(pair, points->y[i]); 889 | add_next_index_zval(return_value, pair); 890 | #if PHP_VERSION_ID >= 70000 891 | efree(pair); 892 | #endif 893 | } 894 | } 895 | 896 | geo_array_dtor(points); 897 | } 898 | /* }}} */ 899 | 900 | static geo_array *interpolate_line(geo_array *points, double epsilon) 901 | { 902 | int i; 903 | geo_array *new_array; 904 | double dx, dy, distance, step_size, res_lat, res_long, fraction; 905 | 906 | new_array = geo_array_ctor(0); 907 | 908 | for (i = 0; i < points->count - 1; i++) { 909 | dx = fabs(points->x[i] - points->x[i + 1]); 910 | dy = fabs(points->y[i] - points->y[i + 1]); 911 | distance = sqrt((dx * dx) + (dy * dy)); 912 | if (distance > epsilon) { 913 | step_size = epsilon/distance; 914 | for (fraction = 0; fraction < 1; fraction += step_size) { 915 | php_geo_fraction_along_gc_line( 916 | points->y[i] * GEO_DEG_TO_RAD, 917 | points->x[i] * GEO_DEG_TO_RAD, 918 | points->y[i + 1] * GEO_DEG_TO_RAD, 919 | points->x[i + 1] * GEO_DEG_TO_RAD, 920 | fraction, 921 | &res_lat, &res_long 922 | ); 923 | geo_array_add(new_array, res_long / GEO_DEG_TO_RAD, res_lat / GEO_DEG_TO_RAD); 924 | } 925 | } else { 926 | geo_array_add(new_array, points->x[i], points->y[i]); 927 | } 928 | } 929 | geo_array_add(new_array, points->x[points->count - 1], points->y[points->count - 1]); 930 | 931 | return new_array; 932 | } 933 | 934 | /* {{{ proto array interpolate_linestring(GeoJSONLineString line, float epsilon) 935 | Interpolates lines with intermediate points to show line segments as GC lines */ 936 | PHP_FUNCTION(interpolate_linestring) 937 | { 938 | zval *line; 939 | double epsilon; 940 | geo_array *points; 941 | int i; 942 | zval *pair; 943 | geo_array *new_array; 944 | 945 | if (zend_parse_parameters(ZEND_NUM_ARGS(), "zd", &line, &epsilon) == FAILURE) { 946 | return; 947 | } 948 | 949 | if (!geojson_linestring_to_array(line, &points)) { 950 | RETURN_FALSE; 951 | } 952 | 953 | array_init(return_value); 954 | 955 | new_array = interpolate_line(points, epsilon); 956 | 957 | for (i = 0; i < new_array->count; i++) { 958 | if (new_array->status[i]) { 959 | GEOSPAT_MAKE_STD_ZVAL(pair); 960 | array_init(pair); 961 | add_next_index_double(pair, new_array->x[i]); 962 | add_next_index_double(pair, new_array->y[i]); 963 | add_next_index_zval(return_value, pair); 964 | #if PHP_VERSION_ID >= 70000 965 | efree(pair); 966 | #endif 967 | } 968 | } 969 | 970 | geo_array_dtor(points); 971 | geo_array_dtor(new_array); 972 | } 973 | /* }}} */ 974 | 975 | /* {{{ string geohash_encode(GeoJSONPoint $point [, int $precision = 12]) 976 | */ 977 | PHP_FUNCTION(geohash_encode) 978 | { 979 | double longitude, latitude; 980 | #if PHP_VERSION_ID >= 70000 981 | zend_long precision = 12; 982 | #else 983 | long precision = 12; 984 | #endif 985 | zval *geojson; 986 | char *hash; 987 | 988 | if (zend_parse_parameters(ZEND_NUM_ARGS(), "al", &geojson, &precision) == FAILURE) { 989 | return; 990 | } 991 | 992 | if (!geojson_point_to_lon_lat(geojson, &longitude, &latitude)) { 993 | RETURN_FALSE; 994 | } 995 | 996 | hash = php_geo_geohash_encode(latitude, longitude, precision); 997 | #if PHP_VERSION_ID >= 70000 998 | RETVAL_STRING(hash); 999 | efree(hash); 1000 | #else 1001 | RETVAL_STRING(hash, 0); 1002 | #endif 1003 | } 1004 | 1005 | /* {{{ string geohash_decode(string $geohash) 1006 | */ 1007 | PHP_FUNCTION(geohash_decode) 1008 | { 1009 | char *hash; 1010 | #if PHP_VERSION_ID >= 70000 1011 | size_t hash_len; 1012 | #else 1013 | int hash_len; 1014 | #endif 1015 | 1016 | if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &hash, &hash_len) == FAILURE) { 1017 | return; 1018 | } 1019 | 1020 | geo_lat_long area = php_geo_geohash_decode(hash, hash_len); 1021 | 1022 | retval_point_from_coordinates(return_value, area.y, area.x); 1023 | } 1024 | 1025 | /* }}}*/ 1026 | 1027 | /* 1028 | * Local variables: 1029 | * tab-width: 4 1030 | * c-basic-offset: 4 1031 | * End: 1032 | * vim600: noet sw=4 ts=4 fdm=marker 1033 | * vim<600: noet sw=4 ts=4 1034 | */ 1035 | -------------------------------------------------------------------------------- /package.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | geospatial 8 | pecl.php.net 9 | PHP Extension to handle common geospatial functions 10 | The extension currently has implementations of the Haversine and 11 | Vincenty's formulas for calculating distances, an initial bearing calculation 12 | function, a Helmert transformation function to transfer between different 13 | supported datums, conversions between polar and Cartesian coordinates, 14 | conversions between Degree/Minute/Seconds and decimal degrees, a method to 15 | simplify linear geometries, as well as a method to calculate intermediate 16 | points on a LineString. 17 | 18 | Derick Rethans 19 | derick 20 | derick@php.net 21 | yes 22 | 23 | 24 | Michael MacClean 25 | mgdm 26 | mgdm@php.net 27 | yes 28 | 29 | 30 | 2022-08-11 31 | 32 | 33 | 0.3.2 34 | 0.3.2 35 | 36 | 37 | beta 38 | beta 39 | 40 | PHP 3.01 41 | 42 | - Fixed a bug with polar_to_cartesian and cartesian_to_polar having the wrong arginfo 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 8.0.0 64 | 65 | 66 | 1.4.0b1 67 | 68 | 69 | 70 | geospatial 71 | 72 | 73 | 76 | -------------------------------------------------------------------------------- /php_geospatial.h: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | PHP | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) 1997-2022 The PHP Group | 6 | +----------------------------------------------------------------------+ 7 | | This source file is subject to version 3.01 of the PHP license, | 8 | | that is bundled with this package in the file LICENSE, and is | 9 | | available through the world-wide-web at the following url: | 10 | | http://www.php.net/license/3_01.txt | 11 | | If you did not receive a copy of the PHP license and are unable to | 12 | | obtain it through the world-wide-web, please send a note to | 13 | | license@php.net so we can mail you a copy immediately. | 14 | +----------------------------------------------------------------------+ 15 | | Authors: Derick Rethans | 16 | | Michael Maclean | 17 | | Nathaniel McHugh | 18 | | Marcus Deglos | 19 | +----------------------------------------------------------------------+ 20 | */ 21 | 22 | #ifndef PHP_GEOSPATIAL_H 23 | #define PHP_GEOSPATIAL_H 24 | 25 | #define PHP_GEOSPATIAL_VERSION "0.3.2" 26 | 27 | extern zend_module_entry geospatial_module_entry; 28 | #define phpext_geospatial_ptr &geospatial_module_entry 29 | 30 | #ifdef PHP_WIN32 31 | # define PHP_GEOSPATIAL_API __declspec(dllexport) 32 | #elif defined(__GNUC__) && __GNUC__ >= 4 33 | # define PHP_GEOSPATIAL_API __attribute__ ((visibility("default"))) 34 | #else 35 | # define PHP_GEOSPATIAL_API 36 | #endif 37 | 38 | #ifdef ZTS 39 | #include "TSRM.h" 40 | #endif 41 | 42 | typedef struct { 43 | double a; 44 | double b; 45 | double f; 46 | } geo_ellipsoid; 47 | 48 | typedef struct { 49 | double x; 50 | double y; 51 | double z; 52 | } geo_cartesian; 53 | 54 | /** 55 | * Structure of the seven variables used in the helmert transformation 56 | * 57 | */ 58 | typedef struct { 59 | double translation_x; 60 | double translation_y; 61 | double translation_z; 62 | double scale_change; 63 | double rotation_x; 64 | double rotation_y; 65 | double rotation_z; 66 | } geo_helmert_constants; 67 | 68 | #define GEO_DEG_TO_RAD 0.017453292519943295769236907684886 69 | /** 70 | * Calculate the radius using WGS-84's equatorial radius of 71 | * 6,378,137.0m 72 | */ 73 | #define GEO_EARTH_RADIUS 6378137.0 74 | #define GEO_SEC_IN_DEG 3600 75 | 76 | #define GEO_WGS84 0x0001 77 | #define GEO_AIRY_1830 0x0002 78 | 79 | #define HEIGHT 24.7 80 | 81 | 82 | PHP_MINIT_FUNCTION(geospatial); 83 | PHP_MINFO_FUNCTION(geospatial); 84 | 85 | PHP_FUNCTION(haversine); 86 | PHP_FUNCTION(initial_bearing); 87 | PHP_FUNCTION(fraction_along_gc_line); 88 | PHP_FUNCTION(helmert); 89 | PHP_FUNCTION(polar_to_cartesian); 90 | PHP_FUNCTION(cartesian_to_polar); 91 | PHP_FUNCTION(transform_datum); 92 | PHP_FUNCTION(dms_to_decimal); 93 | PHP_FUNCTION(decimal_to_dms); 94 | PHP_FUNCTION(vincenty); 95 | PHP_FUNCTION(rdp_simplify); 96 | PHP_FUNCTION(interpolate_linestring); 97 | PHP_FUNCTION(interpolate_polygon); 98 | PHP_FUNCTION(geohash_encode); 99 | PHP_FUNCTION(geohash_decode); 100 | 101 | #endif /* PHP_GEOSPATIAL_H */ 102 | 103 | /* 104 | * Local variables: 105 | * tab-width: 4 106 | * c-basic-offset: 4 107 | * End: 108 | * vim600: noet sw=4 ts=4 fdm=marker 109 | * vim<600: noet sw=4 ts=4 110 | */ 111 | -------------------------------------------------------------------------------- /php_geospatial.stub.php: -------------------------------------------------------------------------------- 1 | 'Point', 'coordinates' => array( $long, $lat ) ); 10 | 11 | $polar = transform_datum($from, GEO_WGS84, GEO_AIRY_1830); 12 | 13 | $diferenceWGS84 = haversine( 14 | $from, 15 | array('type' => 'Point', 'coordinates' => array( 0, $lat ) ) 16 | ); 17 | 18 | $diferenceAiry = haversine( 19 | $polar, 20 | array('type' => 'Point', 'coordinates' => array( 0, $polar['coordinates'][1] ) ) 21 | ); 22 | //lat long of merdian in Airy 1830 ideally long of 0 23 | var_dump($polar); 24 | //distance in m of difference from lat long and meridian 25 | echo round($diferenceWGS84, 8),PHP_EOL; 26 | echo round($diferenceAiry, 8),PHP_EOL; 27 | ?> 28 | --EXPECTF-- 29 | array(2) { 30 | ["type"]=> 31 | string(5) "Point" 32 | ["coordinates"]=> 33 | array(2) { 34 | [0]=> 35 | float(0.000136273547670%d) 36 | [1]=> 37 | float(51.47740082331%d) 38 | } 39 | } 40 | 102.84185171 41 | 9.44816796 42 | -------------------------------------------------------------------------------- /tests/JodrellBank.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | WGS84 to OSGB36 3 | --FILE-- 4 | 'Point', 'coordinates' => array( $long, $lat ) ); 10 | 11 | $polar = transform_datum($from, GEO_WGS84, GEO_AIRY_1830); 12 | 13 | var_dump($polar); 14 | ?> 15 | --EXPECTF-- 16 | array(2) { 17 | ["type"]=> 18 | string(5) "Point" 19 | ["coordinates"]=> 20 | array(2) { 21 | [0]=> 22 | float(-2.305717162853%d) 23 | [1]=> 24 | float(53.23597401554%d) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/OSGB36_to_WGS84.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | OSGB36 to WGS84 3 | --FILE-- 4 | 'Point', 'coordinates' => array( $long, $lat ) ); 10 | 11 | $polar = transform_datum($from, GEO_AIRY_1830, GEO_WGS84); 12 | 13 | var_dump(decimal_to_dms($polar['coordinates'][1], 'latitude')); 14 | var_dump(decimal_to_dms($polar['coordinates'][0] ,'longitude')); 15 | ?> 16 | --EXPECTF-- 17 | array(4) { 18 | ["degrees"]=> 19 | int(53) 20 | ["minutes"]=> 21 | int(14) 22 | ["seconds"]=> 23 | float(11.4933726727%d) 24 | ["direction"]=> 25 | string(1) "N" 26 | } 27 | array(4) { 28 | ["degrees"]=> 29 | int(2) 30 | ["minutes"]=> 31 | int(18) 32 | ["seconds"]=> 33 | float(30.81779465924%d) 34 | ["direction"]=> 35 | string(1) "W" 36 | } 37 | -------------------------------------------------------------------------------- /tests/WGS84_to_OSGB36.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | WGS84 to OSGB36 3 | --FILE-- 4 | 'Point', 11 | 'coordinates' => array( $long, $lat ) 12 | ); 13 | 14 | $polar = transform_datum($coordinate, GEO_WGS84, GEO_AIRY_1830); 15 | 16 | echo round($polar['coordinates'][1], 8),PHP_EOL; 17 | echo round($polar['coordinates'][0], 8),PHP_EOL; 18 | ?> 19 | --EXPECT-- 20 | 53.38334018 21 | -1.46541628 22 | -------------------------------------------------------------------------------- /tests/bug0016.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test for issue #16: Segfault on rdp_simplify for GeoJSON 3 | --FILE-- 4 | getMessage(), "\n"; 11 | } 12 | ?> 13 | --EXPECT-- 14 | Expected a coordinate pair as an array, but string given 15 | -------------------------------------------------------------------------------- /tests/cartesian_to_polar.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test cartesian to polar 3 | --FILE-- 4 | 14 | --EXPECT-- 15 | 53.383611 16 | 1.466944 17 | 24.7 18 | -------------------------------------------------------------------------------- /tests/decimal_to_dms.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | degrees minutes seconds 3 | --FILE-- 4 | 12 | --EXPECTF-- 13 | array(4) { 14 | ["degrees"]=> 15 | int(1) 16 | ["minutes"]=> 17 | int(2) 18 | ["seconds"]=> 19 | float(3.450000001199%d) 20 | ["direction"]=> 21 | string(1) "W" 22 | } 23 | array(4) { 24 | ["degrees"]=> 25 | int(1) 26 | ["minutes"]=> 27 | int(2) 28 | ["seconds"]=> 29 | float(3.450000001199%d) 30 | ["direction"]=> 31 | string(1) "S" 32 | } 33 | -------------------------------------------------------------------------------- /tests/dms_to_decimal.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | dms_to_decimal() 3 | --FILE-- 4 | 19 | --EXPECTF-- 20 | float(-1.034291666666%d) 21 | float(-2.307138888888%d) 22 | float(-2.307138888888%d) 23 | float(-2.307138888888%d) 24 | float(-0.102%d) 25 | float(-74.57527777777%d) 26 | -------------------------------------------------------------------------------- /tests/fraction_along-001.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test for "fraction_along_gc_line" #1 3 | --FILE-- 4 | 'Point', 'coordinates' => [ 5, 10 ] ]; 6 | $point2 = [ 'type' => 'Point', 'coordinates' => [ 15, 10 ] ]; 7 | 8 | var_dump(fraction_along_gc_line($point1, $point2, 0)); 9 | var_dump(fraction_along_gc_line($point1, $point2, 0.2)); 10 | var_dump(fraction_along_gc_line($point1, $point2, 0.4)); 11 | var_dump(fraction_along_gc_line($point1, $point2, 0.6)); 12 | var_dump(fraction_along_gc_line($point1, $point2, 0.8)); 13 | var_dump(fraction_along_gc_line($point1, $point2, 1)); 14 | ?> 15 | --EXPECTF-- 16 | array(2) { 17 | ["type"]=> 18 | string(5) "Point" 19 | ["coordinates"]=> 20 | array(2) { 21 | [0]=> 22 | float(5) 23 | [1]=> 24 | float(10) 25 | } 26 | } 27 | array(2) { 28 | ["type"]=> 29 | string(5) "Point" 30 | ["coordinates"]=> 31 | array(2) { 32 | [0]=> 33 | float(6.999852234726%d) 34 | [1]=> 35 | float(10.02394494379%d) 36 | } 37 | } 38 | array(2) { 39 | ["type"]=> 40 | string(5) "Point" 41 | ["coordinates"]=> 42 | array(2) { 43 | [0]=> 44 | float(8.999926079127%d) 45 | [1]=> 46 | float(10.03592515633%d) 47 | } 48 | } 49 | array(2) { 50 | ["type"]=> 51 | string(5) "Point" 52 | ["coordinates"]=> 53 | array(2) { 54 | [0]=> 55 | float(11.00007392087%d) 56 | [1]=> 57 | float(10.03592515633%d) 58 | } 59 | } 60 | array(2) { 61 | ["type"]=> 62 | string(5) "Point" 63 | ["coordinates"]=> 64 | array(2) { 65 | [0]=> 66 | float(13.00014776527%d) 67 | [1]=> 68 | float(10.02394494379%d) 69 | } 70 | } 71 | array(2) { 72 | ["type"]=> 73 | string(5) "Point" 74 | ["coordinates"]=> 75 | array(2) { 76 | [0]=> 77 | float(%r(15|14.9999)%r%S) 78 | [1]=> 79 | float(10) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /tests/fraction_along-002.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test for "fraction_along_gc_line" #2 3 | --FILE-- 4 | 'Point', 'coordinates' => [ 0.1062, 51.5171 ] ]; 6 | $point2 = [ 'type' => 'Point', 'coordinates' => [ 3.2200, 55.9500 ] ]; 7 | 8 | var_dump(fraction_along_gc_line($point1, $point2, 0.5)); 9 | ?> 10 | --EXPECTF-- 11 | array(2) { 12 | ["type"]=> 13 | string(5) "Point" 14 | ["coordinates"]=> 15 | array(2) { 16 | [0]=> 17 | float(1.580948127199%d) 18 | [1]=> 19 | float(53.74361133415%d) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/fraction_along-003.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test for "fraction_along_gc_line" #3 3 | --FILE-- 4 | 'Point', 'coordinates' => [ 0, 90 ] ]; 6 | $point2 = [ 'type' => 'Point', 'coordinates' => [ 180, 0 ] ]; 7 | var_dump(fraction_along_gc_line($point1, $point2, 0.5)); 8 | 9 | $point1 = [ 'type' => 'Point', 'coordinates' => [ 0, 90 ] ]; 10 | $point2 = [ 'type' => 'Point', 'coordinates' => [ 3, -90 ] ]; 11 | var_dump(fraction_along_gc_line($point1, $point2, 0.5)); 12 | ?> 13 | --EXPECT-- 14 | array(2) { 15 | ["type"]=> 16 | string(5) "Point" 17 | ["coordinates"]=> 18 | array(2) { 19 | [0]=> 20 | float(180) 21 | [1]=> 22 | float(45) 23 | } 24 | } 25 | array(2) { 26 | ["type"]=> 27 | string(5) "Point" 28 | ["coordinates"]=> 29 | array(2) { 30 | [0]=> 31 | float(1.5) 32 | [1]=> 33 | float(0) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/geohash_decode.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test geohash_decode 3 | --SKIPIF-- 4 | 5 | --FILE-- 6 | 28 | --EXPECTF-- 29 | array(2) { 30 | ["type"]=> 31 | string(5) "Point" 32 | ["coordinates"]=> 33 | array(2) { 34 | [0]=> 35 | float(157.5) 36 | [1]=> 37 | float(67.5) 38 | } 39 | } 40 | array(2) { 41 | ["type"]=> 42 | string(5) "Point" 43 | ["coordinates"]=> 44 | array(2) { 45 | [0]=> 46 | float(174.375) 47 | [1]=> 48 | float(47.8125) 49 | } 50 | } 51 | array(2) { 52 | ["type"]=> 53 | string(5) "Point" 54 | ["coordinates"]=> 55 | array(2) { 56 | [0]=> 57 | float(170.859375) 58 | [1]=> 59 | float(49.921875) 60 | } 61 | } 62 | array(2) { 63 | ["type"]=> 64 | string(5) "Point" 65 | ["coordinates"]=> 66 | array(2) { 67 | [0]=> 68 | float(171.03515625) 69 | [1]=> 70 | float(49.658203125) 71 | } 72 | } 73 | array(2) { 74 | ["type"]=> 75 | string(5) "Point" 76 | ["coordinates"]=> 77 | array(2) { 78 | [0]=> 79 | float(171.01318359375) 80 | [1]=> 81 | float(49.68017578125) 82 | } 83 | } 84 | array(2) { 85 | ["type"]=> 86 | string(5) "Point" 87 | ["coordinates"]=> 88 | array(2) { 89 | [0]=> 90 | float(171.0296630859%d) 91 | [1]=> 92 | float(49.67193603515%d) 93 | } 94 | } 95 | array(2) { 96 | ["type"]=> 97 | string(5) "Point" 98 | ["coordinates"]=> 99 | array(2) { 100 | [0]=> 101 | float(171.0289764404%d) 102 | [1]=> 103 | float(49.6739959716%d) 104 | } 105 | } 106 | array(2) { 107 | ["type"]=> 108 | string(5) "Point" 109 | ["coordinates"]=> 110 | array(2) { 111 | [0]=> 112 | float(171.028461456%d) 113 | [1]=> 114 | float(49.67408180236%d) 115 | } 116 | } 117 | array(2) { 118 | ["type"]=> 119 | string(5) "Point" 120 | ["coordinates"]=> 121 | array(2) { 122 | [0]=> 123 | float(171.0286116%d) 124 | [1]=> 125 | float(49.67414617538%d) 126 | } 127 | } 128 | array(2) { 129 | ["type"]=> 130 | string(5) "Point" 131 | ["coordinates"]=> 132 | array(2) { 133 | [0]=> 134 | float(171.0285955667%d) 135 | [1]=> 136 | float(49.67415422201%d) 137 | } 138 | } 139 | array(2) { 140 | ["type"]=> 141 | string(5) "Point" 142 | ["coordinates"]=> 143 | array(2) { 144 | [0]=> 145 | float(171.028596237%d) 146 | [1]=> 147 | float(49.67415355145%d) 148 | } 149 | } 150 | array(2) { 151 | ["type"]=> 152 | string(5) "Point" 153 | ["coordinates"]=> 154 | array(2) { 155 | [0]=> 156 | float(171.0285967402%d) 157 | [1]=> 158 | float(49.67415413819%d) 159 | } 160 | } 161 | array(2) { 162 | ["type"]=> 163 | string(5) "Point" 164 | ["coordinates"]=> 165 | array(2) { 166 | [0]=> 167 | float(-5.60302734375) 168 | [1]=> 169 | float(42.60498046875) 170 | } 171 | } 172 | array(2) { 173 | ["type"]=> 174 | string(5) "Point" 175 | ["coordinates"]=> 176 | array(2) { 177 | [0]=> 178 | float(157.5) 179 | [1]=> 180 | float(67.5) 181 | } 182 | } 183 | array(2) { 184 | ["type"]=> 185 | string(5) "Point" 186 | ["coordinates"]=> 187 | array(2) { 188 | [0]=> 189 | float(16.4000000618%d) 190 | [1]=> 191 | float(48.1999999936%d) 192 | } 193 | } 194 | array(2) { 195 | ["type"]=> 196 | string(5) "Point" 197 | ["coordinates"]=> 198 | array(2) { 199 | [0]=> 200 | float(179.9999998323%d) 201 | [1]=> 202 | float(89.9999999161%d) 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /tests/geohash_encode.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test geohash_encode 3 | --SKIPIF-- 4 | 5 | --FILE-- 6 | 'Point', 'coordinates' => [16.4, 48.2]), 12).PHP_EOL; 8 | echo geohash_encode(array('type' => 'Point', 'coordinates' => [90, 90]), 6).PHP_EOL; 9 | echo geohash_encode(array('type' => 'Point', 'coordinates' => [16.363, 48.21]), 32).PHP_EOL; 10 | echo geohash_encode(array('type' => 'Point', 'coordinates' => [95, 95]), 6).PHP_EOL; 11 | echo geohash_encode(array('type' => 'Point', 'coordinates' => [185, 185]), 12).PHP_EOL; 12 | echo geohash_encode(array('type' => 'Point', 'coordinates' => [-90, -185]), 12).PHP_EOL; 13 | echo var_dump(geohash_encode(array('type' => 'Point', 'coordinates' => [30, 30]), 0)); 14 | echo var_dump(geohash_encode(array('type' => 'Point', 'coordinates' => [30, 30]), -1)); 15 | ?> 16 | --EXPECT-- 17 | u2edjnw17enr 18 | vzzzzz 19 | u2edk275te35u5s6504t7yfpbpbpbpbp 20 | ypgxcz 21 | zzzzzzzzzzzz 22 | 1bpbpbpbpbpb 23 | string(0) "" 24 | string(0) "" 25 | -------------------------------------------------------------------------------- /tests/geospatial_haversine_london_edinburgh.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check the haversine function returns the correct distance between London and Edinburgh. 3 | --SKIPIF-- 4 | 5 | --INI-- 6 | precision=14 7 | --FILE-- 8 | 'Point', 11 | 'coordinates' => array( 0.1062, 51.5171 ) 12 | ); 13 | $to = array( 14 | 'type' => 'Point', 15 | 'coordinates' => array( 3.2200, 55.9500 ) 16 | ); 17 | echo haversine($from, $to); 18 | /* 19 | Test the haversine distance between London and Edinburgh. 20 | 21 | London: 51.5171° N, 0.1062° W (0.899143016 / 0.00185353967) 22 | Edinburgh: 55.9500° N, 3.2200° W (0.976511716 / 0.0561996019) 23 | 24 | Delta-lat: 4.4329° (0.0773687004) 25 | Delta-long: 3.1138° (0.0543460622) 26 | 27 | double a = pow(sin(0.0773687004 * 0.5),2) + cos(0.899143016) * cos(0.976511716) * pow(sin(0.0543460622 * 0.5),2); 28 | double a = pow(sin(0.03868435),2) + cos(0.899143016) * cos(0.976511716) * pow(sin(0.027173031),2); 29 | double a = pow(0.03867470233,2) + 0.62228103855 * 0.55991616262 * pow(0.02716968714,2); 30 | double a = 0.0014957326 + 0.62228103855 * 0.55991616262 * 0.00073819189 31 | double a = 0.0014957326 + 0.00025720466 32 | double a = 0.00175293726 33 | double c = 2.0 * atan2(sqrt(a), sqrt(1-a)); 34 | double c = 2.0 * atan2(sqrt(0.00175293726), sqrt(0.99824706274)); 35 | double c = 2.0 * 0.04188033525 36 | double c = 0.08376067 37 | return R * c; 38 | return 39 | 40 | Accept defined (equatorial) Earth Radius in km (which is based on WGS-84): 41 | - 6,378,1370m (at the equator). 42 | - 6,356,7523m (at the equator). 43 | 44 | Sanity check - expected distance: approx 534km. 45 | */ 46 | ?> 47 | --EXPECT-- 48 | 534237.03599421 49 | 50 | -------------------------------------------------------------------------------- /tests/geospatial_haversine_polar_distance.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check the haversine function returns the correct distance between the North and South poles, using a custom radius. 3 | --SKIPIF-- 4 | 5 | --INI-- 6 | precision=14 7 | --FILE-- 8 | 'Point', 11 | 'coordinates' => array( 0, -90 ) 12 | ); 13 | $to = array( 14 | 'type' => 'Point', 15 | 'coordinates' => array( 0, 90 ) 16 | ); 17 | echo haversine($from, $to, 6356.7523); 18 | /* 19 | Test the haversine distance between the North and South poles. 20 | 21 | Accept defined (equatorial) Earth Radius in km (which is based on WGS-84): 22 | - 6,378,1370m (at the equator). 23 | - 6,356,7523m (around the poles). 24 | */ 25 | ?> 26 | --EXPECT-- 27 | 19970.32632637 28 | -------------------------------------------------------------------------------- /tests/haversine.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | haversine() function - basic test for haversine forumla 3 | --INI-- 4 | precision=15 5 | --FILE-- 6 | 'Point', 9 | 'coordinates' => array( -104.88544, 39.06546 ) 10 | ); 11 | $to = array( 12 | 'type' => 'Point', 13 | 'coordinates' => array( -104.80, 39.06546 ) 14 | ); 15 | var_dump(haversine($to, $from)); 16 | ?> 17 | --EXPECTF-- 18 | float(7384.698392931%d) 19 | -------------------------------------------------------------------------------- /tests/helmert.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | helmert() function - basic test for helmert formula 3 | --FILE-- 4 | 11 | --EXPECTF-- 12 | array(3) { 13 | ["x"]=> 14 | float(3909460.067671%d) 15 | ["y"]=> 16 | float(-146987.301381%d) 17 | ["z"]=> 18 | float(5019888.070593%d) 19 | } 20 | -------------------------------------------------------------------------------- /tests/initial_bearing1.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | initial_bearing() function - basic test for initial_bearing forumla 3 | --FILE-- 4 | 'Point', 7 | 'coordinates' => array( -1, 53 ) 8 | ); 9 | $to = array( 10 | 'type' => 'Point', 11 | 'coordinates' => array( 0, 52 ) 12 | ); 13 | var_dump(initial_bearing($from, $to)); 14 | ?> 15 | --EXPECTF-- 16 | float(148.2708928017%d) 17 | -------------------------------------------------------------------------------- /tests/initial_bearing2.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | initial_bearing() function - basic test for initial_bearing forumla 3 | --FILE-- 4 | 'Point', 7 | 'coordinates' => array( 2.351, 48.857 ) 8 | ); 9 | $to = array( 10 | 'type' => 'Point', 11 | 'coordinates' => array( 0.119, 52.205 ) 12 | ); 13 | var_dump(initial_bearing($from, $to)); 14 | ?> 15 | --EXPECTF-- 16 | float(337.8904401904%d) 17 | -------------------------------------------------------------------------------- /tests/interpolate-line-string-001.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test for "interpolate_linestring" #1 3 | --FILE-- 4 | 'Linestring', 7 | 'coordinates' => [ 8 | [ 5, 10 ], 9 | [ 15, 10 ], 10 | [ 0, -50 ], 11 | ] 12 | ]; 13 | 14 | var_dump(interpolate_linestring($lineString, 3)); 15 | ?> 16 | --EXPECTF-- 17 | array(26) { 18 | [0]=> 19 | array(2) { 20 | [0]=> 21 | float(5) 22 | [1]=> 23 | float(10) 24 | } 25 | [1]=> 26 | array(2) { 27 | [0]=> 28 | float(7.9998706635703245) 29 | [1]=> 30 | float(10.03143197167244) 31 | } 32 | [2]=> 33 | array(2) { 34 | [0]=> 35 | float(11.000073920872426) 36 | [1]=> 37 | float(10.035925156338873) 38 | } 39 | [3]=> 40 | array(2) { 41 | [0]=> 42 | float(14.000110773799255) 43 | [1]=> 44 | float(10.013466491669567) 45 | } 46 | [4]=> 47 | array(2) { 48 | [0]=> 49 | float(14.999999999999998) 50 | [1]=> 51 | float(10) 52 | } 53 | [5]=> 54 | array(2) { 55 | [0]=> 56 | float(14.431497984318062) 57 | [1]=> 58 | float(7.0743500242581225) 59 | } 60 | [6]=> 61 | array(2) { 62 | [0]=> 63 | float(13.87016260996529) 64 | [1]=> 65 | float(4.148019326790382) 66 | } 67 | [7]=> 68 | array(2) { 69 | [0]=> 70 | float(13.312974140472463) 71 | [1]=> 72 | float(1.221294808989993) 73 | } 74 | [8]=> 75 | array(2) { 76 | [0]=> 77 | float(12.756998952507347) 78 | [1]=> 79 | float(-1.7055449208364544) 80 | } 81 | [9]=> 82 | array(2) { 83 | [0]=> 84 | float(12.199328445763877) 85 | [1]=> 86 | float(-4.632223663590317) 87 | } 88 | [10]=> 89 | array(2) { 90 | [0]=> 91 | float(11.63701903368877) 92 | [1]=> 93 | float(-7.55846185163128) 94 | } 95 | [11]=> 96 | array(2) { 97 | [0]=> 98 | float(11.067030621797189) 99 | [1]=> 100 | float(-10.483970622513857) 101 | } 102 | [12]=> 103 | array(2) { 104 | [0]=> 105 | float(10.486160869758637) 106 | [1]=> 107 | float(-13.408445478413974) 108 | } 109 | [13]=> 110 | array(2) { 111 | [0]=> 112 | float(9.890972221197222) 113 | [1]=> 114 | float(-16.331559233911236) 115 | } 116 | [14]=> 117 | array(2) { 118 | [0]=> 119 | float(9.277708136408956) 120 | [1]=> 121 | float(-19.252953899216553) 122 | } 123 | [15]=> 124 | array(2) { 125 | [0]=> 126 | float(8.642194115015831) 127 | [1]=> 128 | float(-22.172231059691807) 129 | } 130 | [16]=> 131 | array(2) { 132 | [0]=> 133 | float(7.979717846837639) 134 | [1]=> 135 | float(-25.08894018486976) 136 | } 137 | [17]=> 138 | array(2) { 139 | [0]=> 140 | float(7.284881023816488) 141 | [1]=> 142 | float(-28.002564114424196) 143 | } 144 | [18]=> 145 | array(2) { 146 | [0]=> 147 | float(6.55141274516024) 148 | [1]=> 149 | float(-30.91250069881375) 150 | } 151 | [19]=> 152 | array(2) { 153 | [0]=> 154 | float(5.771930687062061) 155 | [1]=> 156 | float(-33.81803917851203) 157 | } 158 | [20]=> 159 | array(2) { 160 | [0]=> 161 | float(4.937630724808758) 162 | [1]=> 163 | float(-36.718329304916004) 164 | } 165 | [21]=> 166 | array(2) { 167 | [0]=> 168 | float(4.037877612215368) 169 | [1]=> 170 | float(-39.61234033808264) 171 | } 172 | [22]=> 173 | array(2) { 174 | [0]=> 175 | float(3.059657259010722) 176 | [1]=> 177 | float(-42.49880573947054) 178 | } 179 | [23]=> 180 | array(2) { 181 | [0]=> 182 | float(1.9868328958488306) 183 | [1]=> 184 | float(-45.37614734526896) 185 | } 186 | [24]=> 187 | array(2) { 188 | [0]=> 189 | float(0.7991194226357126) 190 | [1]=> 191 | float(-48.2423696103279) 192 | } 193 | [25]=> 194 | array(2) { 195 | [0]=> 196 | float(0) 197 | [1]=> 198 | float(-50) 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /tests/interpolate-line-string-002.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test for "interpolate_linestring" #2 3 | --FILE-- 4 | 'Linestring', 7 | 'coordinates' => [ 8 | [ 0, 0 ], 9 | [ 90, 0 ], 10 | ] 11 | ]; 12 | 13 | var_dump(interpolate_linestring($lineString, 20)); 14 | ?> 15 | --EXPECTF-- 16 | array(6) { 17 | [0]=> 18 | array(2) { 19 | [0]=> 20 | float(0) 21 | [1]=> 22 | float(0) 23 | } 24 | [1]=> 25 | array(2) { 26 | [0]=> 27 | float(19.999999999999996) 28 | [1]=> 29 | float(0) 30 | } 31 | [2]=> 32 | array(2) { 33 | [0]=> 34 | float(40.00000000000001) 35 | [1]=> 36 | float(0) 37 | } 38 | [3]=> 39 | array(2) { 40 | [0]=> 41 | float(59.99999999999999) 42 | [1]=> 43 | float(0) 44 | } 45 | [4]=> 46 | array(2) { 47 | [0]=> 48 | float(80) 49 | [1]=> 50 | float(0) 51 | } 52 | [5]=> 53 | array(2) { 54 | [0]=> 55 | float(90) 56 | [1]=> 57 | float(0) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/interpolate-line-string-003.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test for "interpolate_linestring" #3 3 | --FILE-- 4 | 'Linestring', 7 | 'coordinates' => [ 8 | [ 0, 50 ], 9 | [ 90, 50 ], 10 | ] 11 | ]; 12 | 13 | var_dump(interpolate_linestring($lineString, 20)); 14 | var_dump(interpolate_linestring($lineString, 40)); 15 | ?> 16 | --EXPECTF-- 17 | array(6) { 18 | [0]=> 19 | array(2) { 20 | [0]=> 21 | float(0) 22 | [1]=> 23 | float(50) 24 | } 25 | [1]=> 26 | array(2) { 27 | [0]=> 28 | float(17.264525298083225) 29 | [1]=> 30 | float(56.16396630005119) 31 | } 32 | [2]=> 33 | array(2) { 34 | [0]=> 35 | float(39.12863273409221) 36 | [1]=> 37 | float(59.18552765040588) 38 | } 39 | [3]=> 40 | array(2) { 41 | [0]=> 42 | float(62.26466104551454) 43 | [1]=> 44 | float(58.14617677212717) 45 | } 46 | [4]=> 47 | array(2) { 48 | [0]=> 49 | float(81.99106566784195) 50 | [1]=> 51 | float(53.39333006084351) 52 | } 53 | [5]=> 54 | array(2) { 55 | [0]=> 56 | float(90) 57 | [1]=> 58 | float(50) 59 | } 60 | } 61 | array(4) { 62 | [0]=> 63 | array(2) { 64 | [0]=> 65 | float(0) 66 | [1]=> 67 | float(50) 68 | } 69 | [1]=> 70 | array(2) { 71 | [0]=> 72 | float(39.12863273409221) 73 | [1]=> 74 | float(59.18552765040588) 75 | } 76 | [2]=> 77 | array(2) { 78 | [0]=> 79 | float(81.99106566784195) 80 | [1]=> 81 | float(53.39333006084351) 82 | } 83 | [3]=> 84 | array(2) { 85 | [0]=> 86 | float(90) 87 | [1]=> 88 | float(50) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /tests/polar_to_cartesian.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test polar to cartesian 3 | --FILE-- 4 | 10 | --EXPECTF-- 11 | array(3) { 12 | ["x"]=> 13 | float(3810891.673439%d) 14 | ["y"]=> 15 | float(97591.624686%d) 16 | ["z"]=> 17 | float(5095766.393903%d) 18 | } 19 | -------------------------------------------------------------------------------- /tests/rdp_simplify.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test for rdp_simplify 3 | --FILE-- 4 | geometry->coordinates[0]; 7 | 8 | $result = RDP_Simplify( $points, 0.001 ); 9 | var_dump( count( $result ) ); 10 | 11 | $result = RDP_Simplify( $points, 0.01 ); 12 | var_dump( count( $result ) ); 13 | ?> 14 | --EXPECT-- 15 | int(1029) 16 | int(261) 17 | -------------------------------------------------------------------------------- /tests/vincenty.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test for "Vincenty" distance between FlindersPeak and Buninyong 3 | --INI-- 4 | precision=15 5 | --FILE-- 6 | 'Point', 14 | 'coordinates' => array( $flindersPeakLong, $flindersPeakLat ) 15 | ); 16 | $buninyong = array( 17 | 'type' => 'Point', 18 | 'coordinates' => array( $buninyongLong, $buninyongLat ) 19 | ); 20 | var_dump(vincenty($flinders, $buninyong)); 21 | ?> 22 | --EXPECTF-- 23 | float(54972.2%d) 24 | --------------------------------------------------------------------------------