php Validate latitude/longitude strings in decimal format
Asked Answered
P

8

27

Alright I have what I would call a massive list of longitude and latitude coordinates. That said I also have a handful of sources I pull these coordinates in from. Some of them come from get/post methods which can cause potential security holes in my site/service. So I am trying to figure out how to validate longitude and latitude via PHP. I was thinking something regex via preg_match. But I could be wrong, maybe there's an easier way someone would like to suggest. I've tried my own concepts, and I have tried various internet brew concepts of trying to find a regex pattern that will validate these for me via preg_match (or similar again if you got a better suggestion I am all ears).

My Last failed attempt prior to finally caving in and coming here was..

preg_match('^(\-?\d+(\.\d+)?),\s*(\-?\d+(\.\d+)?)$', $geoResults['latitude']))

which yields " preg_match() [function.preg-match]: No ending delimiter '^' found " as my error. Last couple attempts I have tried yielded that error so I have no idea what it is or means.

Piker answered 26/9, 2011 at 0:47 Comment(1)
This would also match 400.00,-700 as being a valid lat/lng.Girosol
N
24

Add forward slashes to the beginning and end of the match sequence to make it valid regex syntax:

preg_match('/^(\-?\d+(\.\d+)?),\s*(\-?\d+(\.\d+)?)$/', $geoResults['latitude']);

For your question on whether to use regular expressions (regex) or not, in this case using regex (PCRE preg_match()) is the best way to secure your site. When matching variable complex string arrangements, regex is the way to go. It's common for developers to turn to regex for matching a static string such as 'abc'. This is when strpos() or str_replace() are better choices.

Nearsighted answered 26/9, 2011 at 0:51 Comment(5)
this gets me on the right track however I am using it in an if statement.. if this match is invalid should i be using !preg_match or just preg_match?Piker
@Piker preg_match returns 1 (true) if a match is found, otherwise 0. If you want to check if it hasn't found a match, use !preg_match()Nearsighted
@ChrisBornhoft is not working with -74.052638804=3612Repay
@OscarDavid if you're using = as a delimiter (also 3612 is not a valid lat/lng), you'll need to alter the expression to reflect that.Nearsighted
It's not delimiter... It's a wrong longitude but the expression didn't detect the mistakeRepay
O
40

It's a bit old question, but anyway I post my solution here:

preg_match('/^[-]?(([0-8]?[0-9])\.(\d+))|(90(\.0+)?);[-]?((((1[0-7][0-9])|([0-9]?[0-9]))\.(\d+))|180(\.0+)?)$/', $geoResults['latlng']);

I assumed here that u split lat. from lng. by semicolon. If u want to check only lat. or only lng. here are regexp's;

Rgx for lat.:

/^[-]?(([0-8]?[0-9])\.(\d+))|(90(\.0+)?)$/

Rgx for lng.:

/^[-]?((((1[0-7][0-9])|([0-9]?[0-9]))\.(\d+))|180(\.0+)?)$/

Here is an improved online demo: https://regex101.com/r/bV5fA1/1

Oceanography answered 25/2, 2014 at 7:18 Comment(8)
Why the limit at 80 and 179?Grantham
I've edited my posted and changed the limits to 89.(9) and 179.(9). Why? Becouse of point 179.(9) and -179.(9) are pretty the same and it allows to make rgx easier.Oceanography
Better but not suficient, lat 90 and long 180 are perfectly valid.Grantham
Edited once again. Now lat 90 and lng 180 will be valid ;) Thanks for suggestions.Oceanography
OK, +1 but you should escape the dot in (.0+).Grantham
Done. Thank you for appreciation and your help to finally create a valid answer.Oceanography
[0-8]? should be replaced with [1-9]? to make values like 05.12345 invalid. Anyway, thank you for your regexp! PS: The same with [0-9]? for longitude.Promptitude
I tried to test on 0.000000;0.000000, yes I know they are still valid and point to a certain location but in my case that's never gonna happen and it's a bit of an overkill but other then that your solution is awesome!Besse
N
24

Add forward slashes to the beginning and end of the match sequence to make it valid regex syntax:

preg_match('/^(\-?\d+(\.\d+)?),\s*(\-?\d+(\.\d+)?)$/', $geoResults['latitude']);

For your question on whether to use regular expressions (regex) or not, in this case using regex (PCRE preg_match()) is the best way to secure your site. When matching variable complex string arrangements, regex is the way to go. It's common for developers to turn to regex for matching a static string such as 'abc'. This is when strpos() or str_replace() are better choices.

Nearsighted answered 26/9, 2011 at 0:51 Comment(5)
this gets me on the right track however I am using it in an if statement.. if this match is invalid should i be using !preg_match or just preg_match?Piker
@Piker preg_match returns 1 (true) if a match is found, otherwise 0. If you want to check if it hasn't found a match, use !preg_match()Nearsighted
@ChrisBornhoft is not working with -74.052638804=3612Repay
@OscarDavid if you're using = as a delimiter (also 3612 is not a valid lat/lng), you'll need to alter the expression to reflect that.Nearsighted
It's not delimiter... It's a wrong longitude but the expression didn't detect the mistakeRepay
S
3

Why not use modern and unit tested Assertion library for that?

Example Value Object class:

<?php
namespace MyApp\ValueObject;

use Assert\Assertion;

class Location
{
    private $lat;
    private $lon;

    public function __construct($lat, $lon)
    {
        Assertion::greaterOrEqualThan($lat, -90.0);
        Assertion::lessOrEqualThan($lat, 90.0);
        Assertion::greaterOrEqualThan($lon, -180.0);
        Assertion::lessOrEqualThan($lon, 180.0);

        $this->lat = $lat;
        $this->lon = $lon;
    }

    public function latitude()
    {
        return $this->lat;
    }

    public function longitude()
    {
        return $this->lon;
    }

    public function __toString()
    {
        return $this->lat . ' ' . $this->lon;
    }

Usage:

$location = new \MyApp\ValueObject\Location(24.7, -20.4059);

echo $location->latitude() , ' ' , $location->longitude();

// or simply

echo $location;

PHP8.2 update:

final readonly class Location implements \Stringable
{
    public function __construct(
        private float $lat,
        private float $lon,
    ) {
        Assertion::greaterOrEqualThan($this->lat, -90.0);
        Assertion::lessOrEqualThan($this->lat, 90.0);
        Assertion::greaterOrEqualThan($this->lon, -180.0);
        Assertion::lessOrEqualThan($this->lon, 180.0);
    }

    public function latitude(): float
    {
        return $this->lat;
    }

    public function longitude(): float
    {
        return $this->lon;
    }

    public function __toString(): string
    {
        return $this->lat . ' ' . $this->lon;
    }
}
Slavey answered 19/7, 2016 at 10:52 Comment(0)
A
1

I want to validate latitude and longitude, too, and have this result:

-90.0000 - 90.0000

^-?([0-9]|[1-8][0-9]|90)\.{1}\d{4}$

-180.0000 - 180.0000

^-?([0-9]|[1-9][0-9]|1[0-7][0-9]|180)\.{1}\d{4}$

I have tested here with pcre.

Alectryomancy answered 30/11, 2015 at 0:51 Comment(1)
You should post this as 1 regex to fetch both lat and long from the same line. Also, [0-9]|[1-9][0-9] can be simplified as [1-9]?[0-9].Topliffe
A
1

regex

/([0-9.-]+).+?([0-9.-]+)/
Ambience answered 12/2, 2019 at 12:59 Comment(1)
Please, add explanation for your answerCandor
O
1

Function to validate Latitude

function validateLatitude($lat) {


    $lat_array = explode( '.' , $lat );

    if( sizeof($lat_array) !=2 ){
        return '_n_';
    }

    if ( ! ( is_numeric($lat_array[0]) && $lat_array[0]==round($lat_array[0], 0) && is_numeric($lat_array[1]) && $lat_array[1]==round($lat_array[1], 0)  ) ){
        return '_n_';
    }

    if( $lat >= -90 && $lat <= 90 ){
        return '_s_';
    }
    else {
        return '_n_';
    }

}

Function to validate Longitude

function validateLongitude($long) {

    $long_array = explode( '.' , $long );

    if( sizeof($long_array) !=2 ){
        return '_n_';
    }

    if (!( is_numeric($long_array[0]) && $long_array[0]==round($long_array[0], 0) && is_numeric($long_array[1]) && $long_array[1]==round($long_array[1], 0)  ) ){
        return '_n_';
    }

    if( $long >= -180 && $long <= 180 ){
        return '_s_';
    }
    else {
        return '_n_';
    }

}
Oswell answered 10/4, 2019 at 13:24 Comment(0)
V
0

It's real work unicum solution in net " -90.0000 - 90.0000

^-?([0-9]|[1-8][0-9]|90)\.{1}\d{4}$

-180.0000 - 180.0000

^-?([0-9]|[1-9][0-9]|1[0-7][0-9]|180)\.{1}\d{4}$
"

For &lat=90.000000 &lon=180.000000 :

"/^-?([0-9]|[1-8][0-9]|90)\.{1}\d{1,6}$/"

"/^-?([1]?[1-7][1-9]|[1]?[1-8][0]|[1-9]?[0-9])\.{1}\d{1,6}/"
Vanna answered 1/2, 2016 at 3:46 Comment(0)
O
0

These didn't seem very accurate being that:

latitude is between -90 and 90

longitude is between -180 and 90

and

\d in regex matches more than [0-9]

Also a whole library just for some regex didn't seem logical...

So I built and tested:

//Latitude
if(preg_match('/^-?(90|[1-8][0-9][.][0-9]{1,20}|[0-9][.][0-9]{1,20})$/', '89.33333')) {
                            echo "<br>a match<br>"; 
} //will pass ->89.33333


//Longitude
if(preg_match('/^-?(180|1[1-7][0-9][.][0-9]{1,20}|[1-9][0-9][.][0-9]{1,20}|[0-9][.][0-9]{1,20})$/', '180.33333')) {
                            echo "<br>b match<br>"; 
} // will fail anything over 180 ->180.33333
Overuse answered 9/7, 2021 at 18:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.