Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
81.16% covered (warning)
81.16%
56 / 69
25.00% covered (danger)
25.00%
1 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
GeoCoordinate
81.16% covered (warning)
81.16%
56 / 69
25.00% covered (danger)
25.00%
1 / 4
40.28
0.00% covered (danger)
0.00%
0 / 1
 getLatLonFromString
100.00% covered (success)
100.00%
56 / 56
100.00% covered (success)
100.00%
1 / 1
25
 getFloatFromString
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
42
 moveCoordinatesInMeters
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getDistanceInMeters
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2namespace MultiMaps;
3
4/**
5 * This class provides functions for working with geographic coordinates
6 *
7 * @file GeoCoordinate.php
8 * @ingroup MultiMaps
9 * @author Pavel Astakhov <pastakhov@yandex.ru>
10 * @license GPL-2.0-or-later
11 */
12class GeoCoordinate {
13
14    /**
15     * WGS 84
16     * The length of the Earth's equator, meters
17     */
18    const EQUATOR_LENGTH = 40075017;
19
20    /**
21     * WGS 84
22     * The length of the Earth's meredian, meters
23     */
24    const MEREDIAN_LENGTH = 20003930;
25
26    /**
27     * Converts the string with geographic coordinates to a numeric representation
28     *
29     * @assert ("55.755831°, 37.617673°") == array("lat" => 55.755831, "lon"=> 37.617673)
30     * @assert ("N55.755831°, E37.617673°") == array("lat" => 55.755831, "lon"=> 37.617673)
31     * @assert ("55°45.34986'N, 37°37.06038'E") == array("lat" => 55.755831, "lon"=> 37.617673)
32     * @assert ("55°45'20.9916\"N, 37°37'3.6228\"E") == array("lat" => 55.755831, "lon"=> 37.617673)
33     * @assert (" 37°37'3.6228\"E, 55°45'20.9916\" ") == array("lat" => 55.755831, "lon"=> 37.617673)
34     * @assert (" 37°37'3.6228\", 55°45'20.9916\" N ") == array("lat" => 55.755831, "lon"=> 37.617673)
35     * @assert ("55°45'20.9916\"N, 37°37'3.6228\"") == array("lat" => 55.755831, "lon"=> 37.617673)
36     * @assert ("55°45'20.9916\", E37°37'3.6228\"") == array("lat" => 55.755831, "lon"=> 37.617673)
37     * @assert (" 10  , - 10 ") == array("lat" => 10, "lon"=> -10)
38     * @assert ("-10°,s10 °  ") == array("lat" => -10, "lon"=> -10)
39     * @assert ("s10.123456°,  -1.123°   ") == array("lat" => -10.123456, "lon"=> -1.123)
40     * @assert ("10.123456° N,  1.123° W  ") == array("lat" => 10.123456, "lon"=> -1.123)
41     * @assert ("10.12° W,  1.123° s  ") == array("lat" => -1.123, "lon"=> -10.12)
42     * @assert ("10.12° w,  1.123°") == array("lat" => 1.123, "lon"=> -10.12)
43     * @assert ("Z10.12°,  1.123°") === false
44     * @assert ("10.12°, X1.123°") === false
45     * @assert ("Tralala") === false
46     *
47     * @global string $egMultiMaps_OptionsSeparator
48     * @param string $coords
49     * @return array array( 'lat'=>(float), 'lon'=>(float) ) or FALSE on error
50     */
51    public static function getLatLonFromString( $coords ) {
52        global $egMultiMaps_OptionsSeparator;
53
54        $matches = [];
55        $subject = preg_replace( '/\s+/', '', $coords );
56
57        $array = explode( $egMultiMaps_OptionsSeparator, $subject );
58        if ( count( $array ) == 2 ) {
59            $lat = false;
60            $lon = false;
61            $doubt = false;
62            $matches = [];
63            if ( preg_match( '/^[NSWE]|[NSWE]$/i', $array[0], $matches ) ) {
64                $string = preg_replace( '/[NSWE]/i', '', $array[0], 1 );
65                switch ( strtoupper( $matches[0] ) ) {
66                    case 'N':
67                        $lat = self::getFloatFromString( $string );
68                        break;
69                    case 'S':
70                        $lat = -1 * self::getFloatFromString( $string );
71                        break;
72                    case 'E':
73                        $lon = self::getFloatFromString( $string );
74                        break;
75                    case 'W':
76                        $lon = -1 * self::getFloatFromString( $string );
77                        break;
78                }
79            } else {
80                $doubt = true;
81                $lat = self::getFloatFromString( $array[0] );
82            }
83
84            if ( $lat === false && $lon === false ) {
85                return false;
86            }
87
88            if ( preg_match( '/^[NSWE]|[NSWE]$/i', $array[1], $matches ) ) {
89                $string = preg_replace( '/[NSWE]/i', '', $array[1], 1 );
90                switch ( strtoupper( $matches[0] ) ) {
91                    case 'N':
92                        if ( !$lat || $doubt ) {
93                            if ( $doubt ) {
94                                $lon = $lat;
95                            }
96                            $lat = self::getFloatFromString( $string );
97                        }
98                        break;
99                    case 'S':
100                        if ( !$lat || $doubt ) {
101                            if ( $doubt ) {
102                                $lon = $lat;
103                            }
104                            $lat = -1 * self::getFloatFromString( $string );
105                        }
106                        break;
107                    case 'E':
108                        if ( !$lon ) {
109                            $lon = self::getFloatFromString( $string );
110                        }
111                        break;
112                    case 'W':
113                        if ( !$lon ) {
114                            $lon = -1 * self::getFloatFromString( $string );
115                        }
116                        break;
117                }
118            } else {
119                if ( $lat !== false ) {
120                    $lon = self::getFloatFromString( $array[1] );
121                } else {
122                    $lat = self::getFloatFromString( $array[1] );
123                }
124            }
125
126            if ( $lat !== false && $lon !== false ) {
127                return [ 'lat' => $lat, 'lon' => $lon ];
128            }
129        }
130        return false;
131    }
132
133    /**
134     * Converts the string with coordinate to a float format
135     *
136     * @assert ('55.755831') == 55.755831
137     * @assert ('55.755831°') == 55.755831
138     * @assert ('55°45.34986\'') == 55.755831
139     * @assert ('55°45\'20.9916\"') == 55.755831
140     * @assert ('N55°45\'20.9916\"') === false
141     *
142     * @param string $string
143     * @return float
144     */
145    private static function getFloatFromString( $string ) {
146        $matches = [];
147        // String contain float
148        if ( preg_match( '/^((?:-)?\d{1,3}(?:\.\d{1,20})?)(?:°)?$/', $string, $matches ) ) {
149            return (float)$matches[1];
150        }
151        // String contain DMS
152        if ( preg_match( '/^((?:-)?\d{1,3})°(\d{1,2}(?:\.\d{1,20})?)(?:\′|\')(?:(\d{1,2}(?:\.\d{1,20})?)(?:″|"))?$/', $string, $matches ) ) {
153            return (float)( abs( $matches[1] ) == $matches[1] ? 1 : -1 ) * ( abs( $matches[1] ) + ( isset( $matches[2] ) ? $matches[2] / 60 : 0 ) + ( isset( $matches[3] ) ? $matches[3] / 3600 : 0 ) );
154        }
155        return false;
156    }
157
158    /**
159     * Sets the geographical coordinates in new position according to a given offset on the north and east, in meters
160     * @param float &$lat Latitude of coordinates
161     * @param float &$lon Longitude of coordinates
162     * @param float $nord To the north (meters)
163     * @param float $east To the East (meters)
164     */
165    public static function moveCoordinatesInMeters( &$lat, &$lon, $nord, $east ) {
166        $lat += ( $nord / self::MEREDIAN_LENGTH ) * 180;
167        $lon += ( $east / ( self::EQUATOR_LENGTH * cos( M_PI / 180 * $lat ) ) ) * 360;
168    }
169
170    /**
171     * Returns the distance between two geographical points
172     * @param float $lat1 Latitude geographical point 1
173     * @param float $lon1 Longitude geographical point 1
174     * @param float $lat2 Latitude geographical point 2
175     * @param float $lon2 Longitude geographical point 2
176     * @return float Distance, in meters
177     */
178    public static function getDistanceInMeters( $lat1, $lon1, $lat2, $lon2 ) {
179        $lat = abs( $lat1 - $lat2 );
180        $lon = abs( $lon1 - $lon2 );
181        $distance_lat = ( $lat / 180 ) * self::MEREDIAN_LENGTH;
182        $distance_lon = ( $lon / 360 ) * self::EQUATOR_LENGTH * cos( M_PI / 180 * abs( ( $lat1 + $lat2 ) / 2 ) );
183        return sqrt( pow( $distance_lat, 2 ) + pow( $distance_lon, 2 ) );
184    }
185
186}