latlng.ts 4.13 KB
Newer Older
JOE XMG's avatar
update  
JOE XMG committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import * as L from 'leaflet';
import { IGeocoder, GeocodingCallback, GeocodingResult } from './api';

export interface LatLngOptions {
  /**
   * The next geocoder to use for non-supported queries
   */
  next?: IGeocoder;
  /**
   * The size in meters used for passing to `LatLng.toBounds`
   */
  sizeInMeters: number;
}

/**
 * Parses basic latitude/longitude strings such as `'50.06773 14.37742'`, `'N50.06773 W14.37742'`, `'S 50° 04.064 E 014° 22.645'`, or `'S 50° 4′ 03.828″, W 14° 22′ 38.712″'`
 * @param query the latitude/longitude string to parse
 * @returns the parsed latitude/longitude
 */
export function parseLatLng(query: string): L.LatLng | undefined {
  let match;
  // regex from https://github.com/openstreetmap/openstreetmap-website/blob/master/app/controllers/geocoder_controller.rb
  if ((match = query.match(/^([NS])\s*(\d{1,3}(?:\.\d*)?)\W*([EW])\s*(\d{1,3}(?:\.\d*)?)$/))) {
    // [NSEW] decimal degrees
    return L.latLng(
      (/N/i.test(match[1]) ? 1 : -1) * +match[2],
      (/E/i.test(match[3]) ? 1 : -1) * +match[4]
    );
  } else if (
    (match = query.match(/^(\d{1,3}(?:\.\d*)?)\s*([NS])\W*(\d{1,3}(?:\.\d*)?)\s*([EW])$/))
  ) {
    // decimal degrees [NSEW]
    return L.latLng(
      (/N/i.test(match[2]) ? 1 : -1) * +match[1],
      (/E/i.test(match[4]) ? 1 : -1) * +match[3]
    );
  } else if (
    (match = query.match(
      /^([NS])\s*(\d{1,3})°?\s*(\d{1,3}(?:\.\d*)?)?['′]?\W*([EW])\s*(\d{1,3})°?\s*(\d{1,3}(?:\.\d*)?)?['′]?$/
    ))
  ) {
    // [NSEW] degrees, decimal minutes
    return L.latLng(
      (/N/i.test(match[1]) ? 1 : -1) * (+match[2] + +match[3] / 60),
      (/E/i.test(match[4]) ? 1 : -1) * (+match[5] + +match[6] / 60)
    );
  } else if (
    (match = query.match(
      /^(\d{1,3})°?\s*(\d{1,3}(?:\.\d*)?)?['′]?\s*([NS])\W*(\d{1,3})°?\s*(\d{1,3}(?:\.\d*)?)?['′]?\s*([EW])$/
    ))
  ) {
    // degrees, decimal minutes [NSEW]
    return L.latLng(
      (/N/i.test(match[3]) ? 1 : -1) * (+match[1] + +match[2] / 60),
      (/E/i.test(match[6]) ? 1 : -1) * (+match[4] + +match[5] / 60)
    );
  } else if (
    (match = query.match(
      /^([NS])\s*(\d{1,3})°?\s*(\d{1,2})['′]?\s*(\d{1,3}(?:\.\d*)?)?["″]?\W*([EW])\s*(\d{1,3})°?\s*(\d{1,2})['′]?\s*(\d{1,3}(?:\.\d*)?)?["″]?$/
    ))
  ) {
    // [NSEW] degrees, minutes, decimal seconds
    return L.latLng(
      (/N/i.test(match[1]) ? 1 : -1) * (+match[2] + +match[3] / 60 + +match[4] / 3600),
      (/E/i.test(match[5]) ? 1 : -1) * (+match[6] + +match[7] / 60 + +match[8] / 3600)
    );
  } else if (
    (match = query.match(
      /^(\d{1,3})°?\s*(\d{1,2})['′]?\s*(\d{1,3}(?:\.\d*)?)?["″]\s*([NS])\W*(\d{1,3})°?\s*(\d{1,2})['′]?\s*(\d{1,3}(?:\.\d*)?)?["″]?\s*([EW])$/
    ))
  ) {
    // degrees, minutes, decimal seconds [NSEW]
    return L.latLng(
      (/N/i.test(match[4]) ? 1 : -1) * (+match[1] + +match[2] / 60 + +match[3] / 3600),
      (/E/i.test(match[8]) ? 1 : -1) * (+match[5] + +match[6] / 60 + +match[7] / 3600)
    );
  } else if ((match = query.match(/^\s*([+-]?\d+(?:\.\d*)?)\s*[\s,]\s*([+-]?\d+(?:\.\d*)?)\s*$/))) {
    return L.latLng(+match[1], +match[2]);
  }
}

/**
 * Parses basic latitude/longitude strings such as `'50.06773 14.37742'`, `'N50.06773 W14.37742'`, `'S 50° 04.064 E 014° 22.645'`, or `'S 50° 4′ 03.828″, W 14° 22′ 38.712″'`
 */
export class LatLng implements IGeocoder {
  options: LatLngOptions = {
    next: undefined,
    sizeInMeters: 10000
  };

  constructor(options?: Partial<LatLngOptions>) {
    L.Util.setOptions(this, options);
  }

  geocode(query: string, cb: GeocodingCallback, context?: any) {
    const center = parseLatLng(query);
    if (center) {
      const results: GeocodingResult[] = [
        {
          name: query,
          center: center,
          bbox: center.toBounds(this.options.sizeInMeters)
        }
      ];
      cb.call(context, results);
    } else if (this.options.next) {
      this.options.next.geocode(query, cb, context);
    }
  }
}

/**
 * [Class factory method](https://leafletjs.com/reference.html#class-class-factories) for {@link LatLng}
 * @param options the options
 */
export function latLng(options?: Partial<LatLngOptions>) {
  return new LatLng(options);
}