182

I'm trying to create a regular expression for matching latitude/longitude coordinates. For matching a double-precision number I've used (\-?\d+(\.\d+)?), and tried to combine that into a single expression:

^(\-?\d+(\.\d+)?),\w*(\-?\d+(\.\d+)?)$

I expected this to match a double, a comma, perhaps some space, and another double, but it doesn't seem to work. Specifically it only works if there's NO space, not one or more. What have I done wrong?

Jake Petroules
  • 23,472
  • 35
  • 144
  • 225

20 Answers20

274

This one will strictly match latitude and longitude values that fall within the correct range:

^[-+]?([1-8]?\d(\.\d+)?|90(\.0+)?),\s*[-+]?(180(\.0+)?|((1[0-7]\d)|([1-9]?\d))(\.\d+)?)$

Matches

  • +90.0, -127.554334
  • 45, 180
  • -90, -180
  • -90.000, -180.0000
  • +90, +180
  • 47.1231231, 179.99999999

Doesn't Match

  • -90., -180.
  • +90.1, -100.111
  • -91, 123.456
  • 045, 180
Iain Fraser
  • 6,578
  • 8
  • 43
  • 68
  • 9
    Modified to accept white-space on both sides of the comma: ^[-+]?([1-8]?\d(\.\d+)?|90(\.0+)?)\s*,\s*[-+]?(180(\.0+)?|((1[0-7]\d)|([1-9]?\d))(\.\d+)?)$ – puddinman13 Aug 11 '15 at 15:38
  • Thank you. Also, if you want to capture the polarity (+/-) just add parentheses around both `[+-]` groups. – wuher Nov 24 '16 at 22:47
  • 4
    I changed this to get just that lat lon in the capturing groups by using the `?:` non capturing group syntax, as well as capture polarity `(^[-+]?(?:[1-8]?\d(?:\.\d+)?|90(?:\.0+)?)),\s*([-+]?(?:180(?:\.0+)?|(?:(?:1[0-7]\d)|(?:[1-9]?\d))(?:\.\d+)?))$` – narthur157 May 08 '18 at 15:14
  • 3
    My two cents on this nice regex: added named capturing groups based on @narthur157 and removed some unecessary non capturing groups: `(?^[-+]?(?:[1-8]?\d(?:\.\d+)?|90(?:\.0+)?))\s*,\s*(?[-+]?(?:180(?:\.0+)?|(?:1[0-7]\d|[1-9]?\d)(?:\.\d+)?))$` – Axi May 18 '22 at 12:20
154

I am using these ones (decimal format, with 6 decimal digits):

Latitude

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

Latitude Regular expression visualization

Longitude

^(\+|-)?(?:180(?:(?:\.0{1,6})?)|(?:[0-9]|[1-9][0-9]|1[0-7][0-9])(?:(?:\.[0-9]{1,6})?))$

Longitude Regular expression visualization


Here is a gist that tests both, reported here also, for ease of access. It's a Java TestNG test. You need Slf4j, Hamcrest and Lombok to run it:

import static org.hamcrest.Matchers.*;
import static org.hamcrest.MatcherAssert.*;

import java.math.RoundingMode;
import java.text.DecimalFormat;

import lombok.extern.slf4j.Slf4j;

import org.testng.annotations.Test;

@Slf4j
public class LatLongValidationTest {

    protected static final String LATITUDE_PATTERN="^(\\+|-)?(?:90(?:(?:\\.0{1,6})?)|(?:[0-9]|[1-8][0-9])(?:(?:\\.[0-9]{1,6})?))$";
    protected static final String LONGITUDE_PATTERN="^(\\+|-)?(?:180(?:(?:\\.0{1,6})?)|(?:[0-9]|[1-9][0-9]|1[0-7][0-9])(?:(?:\\.[0-9]{1,6})?))$";

    @Test
    public void latitudeTest(){
        DecimalFormat df = new DecimalFormat("#.######");
        df.setRoundingMode(RoundingMode.UP);
        double step = 0.01;
        Double latitudeToTest = -90.0;

        while(latitudeToTest <= 90.0){
            boolean result = df.format(latitudeToTest).matches(LATITUDE_PATTERN);
            log.info("Latitude: tested {}. Result (matches regex): {}", df.format(latitudeToTest), result);
            assertThat(result, is(true));
            latitudeToTest += step;
        }

        latitudeToTest = -90.1;

        while(latitudeToTest >= -200.0){
            boolean result = df.format(latitudeToTest).matches(LATITUDE_PATTERN);
            log.info("Latitude: tested {}. Result (matches regex): {}", df.format(latitudeToTest), result);
            assertThat(result, is(false));
            latitudeToTest -= step;
        }

        latitudeToTest = 90.01;

        while(latitudeToTest <= 200.0){
            boolean result = df.format(latitudeToTest).matches(LATITUDE_PATTERN);
        log.info("Latitude: tested {}. Result (matches regex): {}", df.format(latitudeToTest), result);
            assertThat(result, is(false));
            latitudeToTest += step;
        }
    }

    @Test
    public void longitudeTest(){
        DecimalFormat df = new DecimalFormat("#.######");
        df.setRoundingMode(RoundingMode.UP);
        double step = 0.01;
        Double longitudeToTest = -180.0;

        while(longitudeToTest <= 180.0){
            boolean result = df.format(longitudeToTest).matches(LONGITUDE_PATTERN);
            log.info("Longitude: tested {}. Result (matches regex): {}", df.format(longitudeToTest), result);
            assertThat(result, is(true));
            longitudeToTest += step;
        }

        longitudeToTest = -180.01;

        while(longitudeToTest >= -300.0){
            boolean result = df.format(longitudeToTest).matches(LONGITUDE_PATTERN);
            log.info("Longitude: tested {}. Result (matches regex): {}", df.format(longitudeToTest), result);
            assertThat(result, is(false));
            longitudeToTest -= step;
        }

        longitudeToTest = 180.01;

        while(longitudeToTest <= 300.0){
            boolean result = df.format(longitudeToTest).matches(LONGITUDE_PATTERN);
            log.info("Longitude: tested {}. Result (matches regex): {}", df.format(longitudeToTest), result);
            assertThat(result, is(false));
            longitudeToTest += step;
        }
    }
}
Marco Ferrari
  • 4,914
  • 5
  • 33
  • 53
138

Whitespace is \s, not \w

^(-?\d+(\.\d+)?),\s*(-?\d+(\.\d+)?)$

See if this works

Miha_x64
  • 5,973
  • 1
  • 41
  • 63
Eric C
  • 3,886
  • 3
  • 30
  • 26
22

Actually Alix Axel, above regex is wrong in latitude, longitude ranges point of view.

Latitude measurements range from –90° to +90° Longitude measurements range from –180° to +180°

So the regex given below validates more accurately.
Also, as per my thought no one should restrict decimal point in latitude/longitude.

^([-+]?\d{1,2}([.]\d+)?),\s*([-+]?\d{1,3}([.]\d+)?)$

OR for Objective C

^([-+]?\\d{1,2}([.]\\d+)?),\\s*([-+]?\\d{1,3}([.]\\d+)?)$
Nicolas Raoul
  • 58,567
  • 58
  • 222
  • 373
Sampada
  • 236
  • 2
  • 3
  • 3
    It accepts`99` for `Latitude`, while `99` is out of range of `-90, +90` and so invalid. – ako Sep 06 '17 at 05:47
18
^-?[0-9]{1,3}(?:\.[0-9]{1,10})?$

Regex breakdown:

^-?[0-9]{1,3}(?:\.[0-9]{1,10})?$

-? # accept negative values

^ # Start of string

[0-9]{1,3} # Match 1-3 digits (i. e. 0-999)

(?: # Try to match...

\. # a decimal point

[0-9]{1,10} # followed by one to 10 digits (i. e. 0-9999999999)

)? # ...optionally

$ # End of string

Jonatas Walker
  • 13,583
  • 5
  • 53
  • 82
Zehra Nasif
  • 181
  • 1
  • 5
  • I think yours is the most elegant. Firstly, it worked immediately without having to edit and replace escape characters. Secondly, it is short. Thirdly, it is easy to understand. – Jim Rota Sep 21 '15 at 20:39
  • the most simple and correct answer... it can be simplied to ^-?\d{1,3}(?:\.\d{1,10})?$ – Saksham Khurana Jun 24 '22 at 12:52
13

@macro-ferrari I did find a way to shorten it, and without look aheads in the light of all recent talks about regex engines

const LAT_RE = /^[+-]?(([1-8]?[0-9])(\.[0-9]{1,6})?|90(\.0{1,6})?)$/;

enter image description here

const LONG_RE = /^[+-]?((([1-9]?[0-9]|1[0-7][0-9])(\.[0-9]{1,6})?)|180(\.0{1,6})?)$/;

enter image description here

Arun Karunagath
  • 1,593
  • 10
  • 24
  • Nice explanation, btw how did u get this flow control any specific software u used. this one https://regexper.com? – silentsudo Jun 02 '18 at 03:49
10

Here is a more strict version:

^([-+]?\d{1,2}[.]\d+),\s*([-+]?\d{1,3}[.]\d+)$
  • Latitude = -90 -- +90
  • Longitude = -180 -- +180
Alix Axel
  • 151,645
  • 95
  • 393
  • 500
10

Try this:

^(\()([-+]?)([\d]{1,2})(((\.)(\d+)(,)))(\s*)(([-+]?)([\d]{1,3})((\.)(\d+))?(\)))$

Check it out at:

http://regexpal.com/

Paste the expression in the top box, then put things like this in the bottom box:

(80.0123, -34.034)
(80.0123)
(80.a)
(980.13, 40)
(99.000, 122.000)

Regex breakdown:

^                    # The string must start this way (there can't be anything before). 
    (\()             # An opening parentheses (escaped with a backslash).
    ([-+]?)          # An optional minus, or an optional plus.
    ([\d]{1,2})      # 1 or 2 digits (0-9).
    (                # Start of a sub-pattern.
        (            # Start of a sub-pattern.
            (\.)     # A dot (escaped with a backslash).
            (\d+)    # One or more digits (0-9).
            (,)      # A comma.
        )            # End of a sub-pattern.
    )                # End of a sub-pattern.
    (\s*)            # Zero or more spaces.
    (                # Start of a sub-pattern.
        ([-+]?)      # An optional minus, or an optional plus. 
        ([\d]{1,3})  # 1 to 3 digits (0-9).
        (            # Start of a pattern.
            (\.)     # A dot (escaped with a backslash).
            (\d+)    # One or more digits (0-9).
        )?           # End of an optional pattern.
        (\))         # A closing parenthesis (escaped with a backkslash).
    )                # End of a pattern
$                    # The string must end this way (there can't be anything after).

Now, what this does NOT do is restrict itself to this range:

(-90 to +90, and -180 to +180)

Instead, it simple restricts itself to this range:

(-99 to +99, -199 to +199) 

But the point is mainly just to break down each piece of the expression.

user1439929
  • 136
  • 1
  • 4
7

Python:

Latitude: result = re.match("^[+-]?((90\.?0*$)|(([0-8]?[0-9])\.?[0-9]*$))", '-90.00001')

Longitude: result = re.match("^[+-]?((180\.?0*$)|(((1[0-7][0-9])|([0-9]{0,2}))\.?[0-9]*$))", '-0.0000')

Latitude should fail in the example.

picmate 涅
  • 3,951
  • 5
  • 43
  • 52
6

Regex shorten @marco-ferrari solution by replacing the multiple use of [0-9] with subset of [0-9]. Also removed unnecessary quantifiers such as ?: from various places

lat  "^([+-])?(?:90(?:\\.0{1,6})?|((?:|[1-8])[0-9])(?:\\.[0-9]{1,6})?)$";
long "^([+-])?(?:180(?:\\.0{1,6})?|((?:|[1-9]|1[0-7])[0-9])(?:\\.[0-9]{1,6})?)$";


**Matches for Lat**

Valid between -90 to +90 with up to 6 decimals.

**Matches for Long**

Valid between -180 to +180 with up to 6 decimals.
jokingAlarm
  • 61
  • 1
  • 2
4

This would work for format like this: 31 ͦ 37.4' E

^[-]?\d{1,2}[ ]*ͦ[ ]*\d{1,2}\.?\d{1,2}[ ]*\x27[ ]*\w$
kenorb
  • 155,785
  • 88
  • 678
  • 743
msaleh
  • 49
  • 1
3

I believe you're using \w (word character) where you ought to be using \s (whitespace). Word characters typically consist of [A-Za-z0-9_], so that excludes your space, which then further fails to match on the optional minus sign or a digit.

theraccoonbear
  • 4,283
  • 3
  • 33
  • 41
2

Ruby

Longitude -179.99999999..180

/^(-?(?:1[0-7]|[1-9])?\d(?:\.\d{1,8})?|180(?:\.0{1,8})?)$/ === longitude.to_s

Latitude -89.99999999..90

/^(-?[1-8]?\d(?:\.\d{1,8})?|90(?:\.0{1,8})?)$/ === latitude.to_s
Zoran Kikic
  • 247
  • 3
  • 7
1

A complete and simple method in objective C for checking correct pattern for latitude and longitude is:

 -( BOOL )textIsValidValue:(NSString*) searchedString
{
    NSRange   searchedRange = NSMakeRange(0, [searchedString length]);
    NSError  *error = nil;
    NSString *pattern = @"^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?),\\s*[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$";
    NSRegularExpression* regex = [NSRegularExpression regularExpressionWithPattern: pattern options:0 error:&error];
    NSTextCheckingResult *match = [regex firstMatchInString:searchedString options:0 range: searchedRange];
    return match ? YES : NO;
}

where searchedString is the input that user would enter in the respective textfield.

1

PHP

Here is the PHP's version (input values are: $latitude and $longitude):

$latitude_pattern  = '/\A[+-]?(?:90(?:\.0{1,18})?|\d(?(?<=9)|\d?)\.\d{1,18})\z/x';
$longitude_pattern = '/\A[+-]?(?:180(?:\.0{1,18})?|(?:1[0-7]\d|\d{1,2})\.\d{1,18})\z/x';
if (preg_match($latitude_pattern, $latitude) && preg_match($longitude_pattern, $longitude)) {
  // Valid coordinates.
}
kenorb
  • 155,785
  • 88
  • 678
  • 743
1

this one enforces 3 numbers after the comma to avoid false matches:

(?<latitude>-?\d+\.\d{3,10}),(?<longitude>-?\d+\.\d{3,10})

Ivan Sanz Carasa
  • 1,141
  • 8
  • 16
0

THIS WILL PERFECTLY WORK ACCORDING TO LAT & LONG STANDARD's

*Latitude Validation Criteria:. Valid between -90 to +90 upto 9 decimals.

*Longitude Validation Criteria:. Valid between -180 to +180 upto 9 decimals.

latitude : "^([+-])?(?:90(?:\.0{1,6})?|((?:|[1-8])[0-9])(?:\.[0-9]{1,9})?)$";

longitude : "^([+-])?(?:180(?:\.0{1,6})?|((?:|[1-9]|1[0-7])[0-9])(?:\.[0-9]{1,9})?)$";

-1

Try this:

(?<!\d)([-+]?(?:[1-8]?\d(?:\.\d+)?|90(?:\.0+)?)),\s*([-+]?(?:180(?:\.0+)?|(?:(?:1[0-7]\d)|(?:[1-9]?\d))(?:\.\d+)?))(?!\d)`
Giordano
  • 5,422
  • 3
  • 33
  • 49
  • 5
    Pure code answers are rarely a good idea. Please add some descriptive text to your answer. – timclutton Dec 04 '14 at 16:22
  • works great: validates accurately, and picks out lat, long from any surrounding text. Does not, however, limit the number of significant digits it allows after the decimal point. – user4325241 Dec 11 '14 at 16:55
-1

You can try this:

var latExp = /^(?=.)-?((8[0-5]?)|([0-7]?[0-9]))?(?:\.[0-9]{1,20})?$/;
var lngExp = /^(?=.)-?((0?[8-9][0-9])|180|([0-1]?[0-7]?[0-9]))?(?:\.[0-9]{1,20})?$/;
Giordano
  • 5,422
  • 3
  • 33
  • 49
-2

Try this:

^[-+]?(([0-8]\\d|\\d)(\\.\\d+)?|90(\\.0+)?)$,\s*^[-+]?((1[0-7]\\d(\\.\\d+)?)|(180(\\.0+)?)|(\\d\\d(\\.\\d+)?)|(\\d(\\.\\d+)?))$
Giordano
  • 5,422
  • 3
  • 33
  • 49
jeremyvillalobos
  • 1,795
  • 2
  • 19
  • 39