├── docs ├── CNAME ├── style.css └── index.html ├── .gitignore ├── .github ├── dependabot.yml └── workflows │ └── build.yml ├── tsconfig.json ├── LICENSE.md ├── CHANGELOG.md ├── package.json ├── scripts └── format-json.js ├── src └── country-coder.ts ├── README.md └── tests └── country-coder.spec.ts /docs/CNAME: -------------------------------------------------------------------------------- 1 | ideditor.codes -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .coverage 3 | .esm-cache 4 | .vscode 5 | .watchmanconfig 6 | 7 | node_modules/ 8 | dist/ 9 | 10 | npm-debug.log 11 | package-lock.json 12 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Please see the documentation for all configuration options: 2 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "npm" 7 | directory: "/" 8 | schedule: 9 | interval: "daily" 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/*"], 3 | "exclude": ["node_modules", "dist"], 4 | "compilerOptions": { 5 | "module": "ES2020", 6 | "declaration": true, 7 | "emitDeclarationOnly": true, 8 | "outDir": "dist", 9 | "esModuleInterop": true, 10 | "moduleResolution": "node", 11 | "noImplicitAny": false, 12 | "noImplicitReturns": true, 13 | "noImplicitThis": true, 14 | "resolveJsonModule": true, 15 | "strictBindCallApply": true, 16 | "strictFunctionTypes": true, 17 | "strictPropertyInitialization": true, 18 | "strictNullChecks": true 19 | }, 20 | "target": "esnext", 21 | "module": "commonjs" 22 | } 23 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ## ISC License 2 | 3 | Copyright (c) 2019-present, country-coder contributors 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 11 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 13 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 14 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 15 | PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: build 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [12.x, 14.x, 16.x] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v1 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | - run: npm install 28 | - run: npm run all 29 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # What's New 2 | 3 | **country-coder** is an open source project. You can submit bug reports, help out, 4 | or learn more by visiting our project page on GitHub: :octocat: https://github.com/ideditor/country-coder 5 | 6 | Please star our project on GitHub to show your support! :star: 7 | 8 | _Breaking changes, which may affect downstream projects, are marked with a_ :warning: 9 | 10 | 11 | 18 | 19 | 20 | # 5.0.3 21 | ##### 2021-Jun-24 22 | * Remove "browser" from the export map ([#45]) 23 | 24 | [#45]: https://github.com/ideditor/country-coder/issues/45 25 | 26 | 27 | # 5.0.2 28 | ##### 2021-Jun-17 29 | * Add an export map to `package.json`, fix file extensions again 30 | 31 | 32 | # 5.0.1 33 | ##### 2021-Jun-15 34 | * Use explicit file extensions for .cjs and .mjs exports ([#44]) 35 | 36 | 37 | # 5.0.0 38 | ##### 2021-Jun-14 39 | * :warning: Replace microbundle with [esbuild](https://esbuild.github.io/) for super fast build speed. Outputs are now: 40 | * `"source": "./src/country-coder.ts"` - TypeScript source file 41 | * `"types": "./dist/country-coder.d.ts"` - TypeScript definition file 42 | * `"main": "./dist/country-coder.cjs"` - CJS bundle, modern JavaScript, works with `require()` 43 | * `"module": "./dist/country-coder.mjs"` - ESM bundle, modern JavaScript, works with `import` 44 | * `"browser": "./dist/country-coder.iife.js"` - IIFE bundle, modern JavaScript, works in browser ` 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 |
22 | 23 |
24 |
25 |

Country Coder

26 |
27 | 28 | 45 |
46 | 47 |
48 | 49 |
50 |
51 | 52 | 1 53 |
54 | 57 |
58 |
59 | 63 |
64 |
65 |
66 | 67 | 68 | 69 |
70 |
71 | 72 |
73 | 76 |
77 |
78 |
79 |
80 | 81 |
82 |
83 | 84 | 520 | 521 | -------------------------------------------------------------------------------- /src/country-coder.ts: -------------------------------------------------------------------------------- 1 | import whichPolygon from 'which-polygon'; 2 | import rawBorders from './data/borders.json'; 3 | 4 | type RegionFeatureProperties = { 5 | // Unique identifier specific to country-coder 6 | id: string; 7 | 8 | // ISO 3166-1 alpha-2 code 9 | iso1A2: string | undefined; 10 | 11 | // ISO 3166-1 alpha-3 code 12 | iso1A3: string | undefined; 13 | 14 | // ISO 3166-1 numeric-3 code 15 | iso1N3: string | undefined; 16 | 17 | // UN M49 code 18 | m49: string | undefined; 19 | 20 | // Wikidata QID 21 | wikidata: string; 22 | 23 | // The emoji flag sequence derived from this feature's ISO 3166-1 alpha-2 code 24 | emojiFlag: string | undefined; 25 | 26 | // The ccTLD (country code top-level domain) 27 | ccTLD: string | undefined; 28 | 29 | // The common English name 30 | nameEn: string; 31 | 32 | // Additional identifiers which can be used to look up this feature; 33 | // these cannot collide with the identifiers for any other feature 34 | aliases: Array | undefined; 35 | 36 | // For features entirely within a country, the ISO 3166-1 alpha-2 code for that country 37 | country: string | undefined; 38 | 39 | // The ISO 3166-1 alpha-2, M49, or QIDs of other features this feature is entirely within, including its country 40 | groups: Array; 41 | 42 | // The ISO 3166-1 alpha-2, M49, or QIDs of other features this feature contains; 43 | // the inverse of `groups` 44 | members: Array | undefined; 45 | 46 | // The rough geographic type of this feature. 47 | // Levels do not necessarily nest cleanly within each other. 48 | // - `world`: all features 49 | 50 | // - `unitedNations`: United Nations 51 | // - `union`: European Union 52 | // - `subunion`: Outermost Regions of the EU, Overseas Countries and Territories of the EU 53 | 54 | // Defined by the UN 55 | // - `region`: Africa, Americas, Antarctica, Asia, Europe, Oceania 56 | // - `subregion`: Sub-Saharan Africa, North America, Micronesia, etc. 57 | // - `intermediateRegion`: Eastern Africa, South America, Channel Islands, etc. 58 | 59 | // - `sharedLandform`: Great Britain, Macaronesia, Mariana Islands, etc. 60 | // - `country`: Ethiopia, Brazil, United States, etc. 61 | // - `subcountryGroup` 62 | // - `territory`: Puerto Rico, Gurnsey, Hong Kong, etc. 63 | // - `subterritory`: Sark, Ascension Island, Diego Garcia, etc. 64 | level: string; 65 | 66 | // The status of this feature's ISO 3166-1 code(s), if any 67 | // - `official`: officially-assigned 68 | // - `excRes`: exceptionally-reserved 69 | // - `usrAssn`: user-assigned 70 | isoStatus: string | undefined; 71 | 72 | // The side of the road that traffic drives on within this feature 73 | // - `right` 74 | // - `left` 75 | driveSide: string | undefined; 76 | 77 | // The unit used for road traffic speeds within this feature 78 | // - `mph`: miles per hour 79 | // - `km/h`: kilometers per hour 80 | roadSpeedUnit: string | undefined; 81 | 82 | // The unit used for road vehicle height restrictions within this feature 83 | // - `ft`: feet and inches 84 | // - `m`: meters 85 | roadHeightUnit: string | undefined; 86 | 87 | // The international calling codes for this feature, sometimes including area codes 88 | // e.g. `1`, `1 340` 89 | callingCodes: Array | undefined; 90 | }; 91 | type RegionFeature = { type: string; geometry: any; properties: RegionFeatureProperties }; 92 | type RegionFeatureCollection = { type: string; features: Array }; 93 | type Vec2 = [number, number]; // [lon, lat] 94 | type Bbox = [number, number, number, number]; // [minLon, minLat, maxLon, maxLat] 95 | type PointGeometry = { type: string; coordinates: Vec2 }; 96 | type PointFeature = { type: string; geometry: PointGeometry; properties: any }; 97 | type Location = Vec2 | PointGeometry | PointFeature; 98 | type CodingOptions = { 99 | // For overlapping features, the division level of the one to get. If no feature 100 | // exists at the given level, the feature at the next higher level is returned. 101 | // See the `level` property of `RegionFeatureProperties` for possible values. 102 | level?: string | undefined; 103 | // Only a feature at the specified level or lower will be returned. 104 | maxLevel?: string | undefined; 105 | // Only a feature with the specified property will be returned. 106 | withProp?: string | undefined; 107 | }; 108 | 109 | // The base GeoJSON feature collection 110 | export let borders: RegionFeatureCollection = rawBorders; 111 | 112 | // The whichPolygon interface for looking up a feature by point 113 | let whichPolygonGetter: any = {}; 114 | // The cache for looking up a feature by identifier 115 | let featuresByCode: any = {}; 116 | 117 | // discard special characters and instances of and/the/of that aren't the only characters 118 | let idFilterRegex = /(?=(?!^(and|the|of|el|la|de)$))(\b(and|the|of|el|la|de)\b)|[-_ .,'()&[\]/]/gi; 119 | 120 | function canonicalID(id: string | null): string { 121 | let s = id || ''; 122 | if (s.charAt(0) === '.') { 123 | // skip replace if it leads with a '.' (e.g. a ccTLD like '.de', '.la') 124 | return s.toUpperCase(); 125 | } else { 126 | return s.replace(idFilterRegex, '').toUpperCase(); 127 | } 128 | } 129 | 130 | // Geographic levels, roughly from most to least granular 131 | let levels = [ 132 | 'subterritory', 133 | 'territory', 134 | 'subcountryGroup', 135 | 'country', 136 | 'sharedLandform', 137 | 'intermediateRegion', 138 | 'subregion', 139 | 'region', 140 | 'subunion', 141 | 'union', 142 | 'unitedNations', 143 | 'world' 144 | ]; 145 | 146 | loadDerivedDataAndCaches(borders); 147 | // Loads implicit feature data and the getter index caches 148 | function loadDerivedDataAndCaches(borders) { 149 | let identifierProps = ['iso1A2', 'iso1A3', 'm49', 'wikidata', 'emojiFlag', 'ccTLD', 'nameEn']; 150 | let geometryFeatures: Array = []; 151 | 152 | for (let i in borders.features) { 153 | let feature = borders.features[i]; 154 | 155 | // generate a unique ID for each feature 156 | feature.properties.id = 157 | feature.properties.iso1A2 || feature.properties.m49 || feature.properties.wikidata; 158 | 159 | loadM49(feature); 160 | loadTLD(feature); 161 | loadIsoStatus(feature); 162 | loadLevel(feature); 163 | loadGroups(feature); 164 | loadFlag(feature); 165 | 166 | // cache only after loading derived IDs 167 | cacheFeatureByIDs(feature); 168 | 169 | if (feature.geometry) geometryFeatures.push(feature); 170 | } 171 | 172 | // must load `members` only after fully loading `featuresByID` 173 | for (let i in borders.features) { 174 | let feature = borders.features[i]; 175 | // ensure all groups are listed by their ID 176 | feature.properties.groups = feature.properties.groups.map(function (groupID) { 177 | return featuresByCode[groupID].properties.id; 178 | }); 179 | 180 | loadMembersForGroupsOf(feature); 181 | } 182 | 183 | for (let i in borders.features) { 184 | let feature = borders.features[i]; 185 | 186 | // must load attributes only after loading geometry features into `members` 187 | loadRoadSpeedUnit(feature); 188 | loadRoadHeightUnit(feature); 189 | loadDriveSide(feature); 190 | loadCallingCodes(feature); 191 | 192 | loadGroupGroups(feature); 193 | } 194 | 195 | for (let i in borders.features) { 196 | let feature = borders.features[i]; 197 | // order groups by their `level` 198 | feature.properties.groups.sort(function (groupID1, groupID2) { 199 | return ( 200 | levels.indexOf(featuresByCode[groupID1].properties.level) - 201 | levels.indexOf(featuresByCode[groupID2].properties.level) 202 | ); 203 | }); 204 | // order members by their `level` and then by order in borders 205 | if (feature.properties.members) 206 | feature.properties.members.sort(function (id1, id2) { 207 | let diff = 208 | levels.indexOf(featuresByCode[id1].properties.level) - 209 | levels.indexOf(featuresByCode[id2].properties.level); 210 | if (diff === 0) { 211 | return ( 212 | borders.features.indexOf(featuresByCode[id1]) - 213 | borders.features.indexOf(featuresByCode[id2]) 214 | ); 215 | } 216 | return diff; 217 | }); 218 | } 219 | 220 | // whichPolygon doesn't support null geometry even though GeoJSON does 221 | let geometryOnlyCollection: RegionFeatureCollection = { 222 | type: 'FeatureCollection', 223 | features: geometryFeatures 224 | }; 225 | whichPolygonGetter = whichPolygon(geometryOnlyCollection); 226 | 227 | function loadGroups(feature: RegionFeature) { 228 | let props = feature.properties; 229 | if (!props.groups) { 230 | props.groups = []; 231 | } 232 | if (feature.geometry && props.country) { 233 | // Add `country` to `groups` 234 | props.groups.push(props.country); 235 | } 236 | if (props.m49 !== '001') { 237 | // all features are in the world feature except the world itself 238 | props.groups.push('001'); 239 | } 240 | } 241 | 242 | function loadM49(feature: RegionFeature) { 243 | let props = feature.properties; 244 | if (!props.m49 && props.iso1N3) { 245 | // M49 is a superset of ISO numerics so we only need to store one 246 | props.m49 = props.iso1N3; 247 | } 248 | } 249 | 250 | function loadTLD(feature: RegionFeature) { 251 | let props = feature.properties; 252 | if (props.level === 'unitedNations') return; // `.un` is not a ccTLD 253 | if (!props.ccTLD && props.iso1A2) { 254 | // ccTLD is nearly the same as iso1A2, so we only need to explicitly code any exceptions 255 | props.ccTLD = '.' + props.iso1A2.toLowerCase(); 256 | } 257 | } 258 | 259 | function loadIsoStatus(feature: RegionFeature) { 260 | let props = feature.properties; 261 | if (!props.isoStatus && props.iso1A2) { 262 | // Features with an ISO code but no explicit status are officially-assigned 263 | props.isoStatus = 'official'; 264 | } 265 | } 266 | 267 | function loadLevel(feature: RegionFeature) { 268 | let props = feature.properties; 269 | if (props.level) return; 270 | if (!props.country) { 271 | // a feature without an explicit `level` or `country` is itself a country 272 | props.level = 'country'; 273 | } else if (!props.iso1A2 || props.isoStatus === 'official') { 274 | props.level = 'territory'; 275 | } else { 276 | props.level = 'subterritory'; 277 | } 278 | } 279 | 280 | function loadGroupGroups(feature: RegionFeature) { 281 | let props = feature.properties; 282 | if (feature.geometry || !props.members) return; 283 | let featureLevelIndex = levels.indexOf(props.level); 284 | let sharedGroups: Array = []; 285 | for (let i in props.members) { 286 | let memberID = props.members[i]; 287 | let member = featuresByCode[memberID]; 288 | let memberGroups = member.properties.groups.filter(function (groupID) { 289 | return ( 290 | groupID !== feature.properties.id && 291 | featureLevelIndex < levels.indexOf(featuresByCode[groupID].properties.level) 292 | ); 293 | }); 294 | if (i === '0') { 295 | sharedGroups = memberGroups; 296 | } else { 297 | sharedGroups = sharedGroups.filter(function (groupID) { 298 | return memberGroups.indexOf(groupID) !== -1; 299 | }); 300 | } 301 | } 302 | props.groups = props.groups.concat( 303 | sharedGroups.filter(function (groupID) { 304 | return props.groups.indexOf(groupID) === -1; 305 | }) 306 | ); 307 | for (let j in sharedGroups) { 308 | let groupFeature = featuresByCode[sharedGroups[j]]; 309 | if (groupFeature.properties.members.indexOf(props.id) === -1) { 310 | groupFeature.properties.members.push(props.id); 311 | } 312 | } 313 | } 314 | 315 | function loadRoadSpeedUnit(feature: RegionFeature) { 316 | let props = feature.properties; 317 | if (feature.geometry) { 318 | // only `mph` regions are listed explicitly, else assume `km/h` 319 | if (!props.roadSpeedUnit) props.roadSpeedUnit = 'km/h'; 320 | } else if (props.members) { 321 | let vals = Array.from( 322 | new Set( 323 | props.members 324 | .map(function (id) { 325 | let member = featuresByCode[id]; 326 | if (member.geometry) return member.properties.roadSpeedUnit || 'km/h'; 327 | }) 328 | .filter(Boolean) 329 | ) 330 | ); 331 | // if all members have the same value then that's also the value for this feature 332 | if (vals.length === 1) props.roadSpeedUnit = vals[0]; 333 | } 334 | } 335 | 336 | function loadRoadHeightUnit(feature: RegionFeature) { 337 | let props = feature.properties; 338 | if (feature.geometry) { 339 | // only `ft` regions are listed explicitly, else assume `m` 340 | if (!props.roadHeightUnit) props.roadHeightUnit = 'm'; 341 | } else if (props.members) { 342 | let vals = Array.from( 343 | new Set( 344 | props.members 345 | .map(function (id) { 346 | let member = featuresByCode[id]; 347 | if (member.geometry) return member.properties.roadHeightUnit || 'm'; 348 | }) 349 | .filter(Boolean) 350 | ) 351 | ); 352 | // if all members have the same value then that's also the value for this feature 353 | if (vals.length === 1) props.roadHeightUnit = vals[0]; 354 | } 355 | } 356 | 357 | function loadDriveSide(feature: RegionFeature) { 358 | let props = feature.properties; 359 | if (feature.geometry) { 360 | // only `left` regions are listed explicitly, else assume `right` 361 | if (!props.driveSide) props.driveSide = 'right'; 362 | } else if (props.members) { 363 | let vals = Array.from( 364 | new Set( 365 | props.members 366 | .map(function (id) { 367 | let member = featuresByCode[id]; 368 | if (member.geometry) return member.properties.driveSide || 'right'; 369 | }) 370 | .filter(Boolean) 371 | ) 372 | ); 373 | // if all members have the same value then that's also the value for this feature 374 | if (vals.length === 1) props.driveSide = vals[0]; 375 | } 376 | } 377 | 378 | function loadCallingCodes(feature: RegionFeature) { 379 | let props = feature.properties; 380 | if (!feature.geometry && props.members) { 381 | props.callingCodes = Array.from( 382 | new Set( 383 | props.members.reduce(function (array, id) { 384 | let member = featuresByCode[id]; 385 | if (member.geometry && member.properties.callingCodes) 386 | return array.concat(member.properties.callingCodes); 387 | return array; 388 | }, []) 389 | ) 390 | ); 391 | } 392 | } 393 | 394 | // Calculates the emoji flag sequence from the alpha-2 code (if any) and caches it 395 | function loadFlag(feature: RegionFeature) { 396 | if (!feature.properties.iso1A2) return; 397 | let flag = feature.properties.iso1A2.replace(/./g, function (char: string) { 398 | return String.fromCodePoint(char.charCodeAt(0) + 127397); 399 | }); 400 | feature.properties.emojiFlag = flag; 401 | } 402 | 403 | // Populate `members` as the inverse relationship of `groups` 404 | function loadMembersForGroupsOf(feature: RegionFeature) { 405 | for (let j in feature.properties.groups) { 406 | let groupID = feature.properties.groups[j]; 407 | let groupFeature = featuresByCode[groupID]; 408 | 409 | if (!groupFeature.properties.members) groupFeature.properties.members = []; 410 | groupFeature.properties.members.push(feature.properties.id); 411 | } 412 | } 413 | 414 | // Caches features by their identifying strings for rapid lookup 415 | function cacheFeatureByIDs(feature: RegionFeature) { 416 | let ids: Array = []; 417 | for (let k in identifierProps) { 418 | let prop = identifierProps[k]; 419 | let id = feature.properties[prop]; 420 | if (id) ids.push(id); 421 | } 422 | if (feature.properties.aliases) { 423 | for (let j in feature.properties.aliases) { 424 | ids.push(feature.properties.aliases[j]); 425 | } 426 | } 427 | for (let i in ids) { 428 | let id = canonicalID(ids[i]); 429 | featuresByCode[id] = feature; 430 | } 431 | } 432 | } 433 | 434 | // Returns the [longitude, latitude] for the location argument 435 | function locArray(loc: Location): Vec2 { 436 | if (Array.isArray(loc)) { 437 | return loc; 438 | } else if ((loc).coordinates) { 439 | return (loc).coordinates; 440 | } 441 | return (loc).geometry.coordinates; 442 | } 443 | 444 | // Returns the smallest feature of any kind containing `loc`, if any 445 | function smallestFeature(loc: Location): RegionFeature | null { 446 | let query = locArray(loc); 447 | let featureProperties: RegionFeatureProperties = whichPolygonGetter(query); 448 | if (!featureProperties) return null; 449 | return featuresByCode[featureProperties.id]; 450 | } 451 | 452 | // Returns the country feature containing `loc`, if any 453 | function countryFeature(loc: Location): RegionFeature | null { 454 | let feature = smallestFeature(loc); 455 | if (!feature) return null; 456 | // a feature without `country` but with geometry is itself a country 457 | let countryCode = feature.properties.country || feature.properties.iso1A2; 458 | return featuresByCode[countryCode] || null; 459 | } 460 | 461 | let defaultOpts = { 462 | level: undefined, 463 | maxLevel: undefined, 464 | withProp: undefined 465 | }; 466 | 467 | // Returns the feature containing `loc` for the `opts`, if any 468 | function featureForLoc(loc: Location, opts: CodingOptions): RegionFeature | null { 469 | let targetLevel = opts.level || 'country'; 470 | let maxLevel = opts.maxLevel || 'world'; 471 | let withProp = opts.withProp; 472 | 473 | let targetLevelIndex = levels.indexOf(targetLevel); 474 | if (targetLevelIndex === -1) return null; 475 | 476 | let maxLevelIndex = levels.indexOf(maxLevel); 477 | if (maxLevelIndex === -1) return null; 478 | if (maxLevelIndex < targetLevelIndex) return null; 479 | 480 | if (targetLevel === 'country') { 481 | // attempt fast path for country-level coding 482 | let fastFeature = countryFeature(loc); 483 | if (fastFeature) { 484 | if (!withProp || fastFeature.properties[withProp]) { 485 | return fastFeature; 486 | } 487 | } 488 | } 489 | 490 | let features = featuresContaining(loc); 491 | 492 | for (let i in features) { 493 | let feature = features[i]; 494 | let levelIndex = levels.indexOf(feature.properties.level); 495 | if ( 496 | feature.properties.level === targetLevel || 497 | // if no feature exists at the target level, return the first feature at the next level up 498 | (levelIndex > targetLevelIndex && levelIndex <= maxLevelIndex) 499 | ) { 500 | if (!withProp || feature.properties[withProp]) { 501 | return feature; 502 | } 503 | } 504 | } 505 | return null; 506 | } 507 | 508 | // Returns the feature with an identifying property matching `id`, if any 509 | function featureForID(id: string | number): RegionFeature | null { 510 | let stringID: string; 511 | 512 | if (typeof id === 'number') { 513 | stringID = id.toString(); 514 | if (stringID.length === 1) { 515 | stringID = '00' + stringID; 516 | } else if (stringID.length === 2) { 517 | stringID = '0' + stringID; 518 | } 519 | } else { 520 | stringID = canonicalID(id); 521 | } 522 | return featuresByCode[stringID] || null; 523 | } 524 | 525 | function smallestFeaturesForBbox(bbox: Bbox): [RegionFeature] { 526 | return whichPolygonGetter.bbox(bbox).map(function (props) { 527 | return featuresByCode[props.id]; 528 | }); 529 | } 530 | 531 | function smallestOrMatchingFeature(query: Location | string | number): RegionFeature | null { 532 | if (typeof query === 'object') { 533 | return smallestFeature(query); 534 | } 535 | return featureForID(query); 536 | } 537 | 538 | // Returns the feature matching the given arguments, if any 539 | export function feature( 540 | query: Location | string | number, 541 | opts: CodingOptions = defaultOpts 542 | ): RegionFeature | null { 543 | if (typeof query === 'object') { 544 | return featureForLoc(query, opts); 545 | } 546 | return featureForID(query); 547 | } 548 | 549 | // Returns the ISO 3166-1 alpha-2 code for the feature matching the arguments, if any 550 | export function iso1A2Code( 551 | query: Location | string | number, 552 | opts: CodingOptions = defaultOpts 553 | ): string | null { 554 | opts.withProp = 'iso1A2'; 555 | let match = feature(query, opts); 556 | if (!match) return null; 557 | return match.properties.iso1A2 || null; 558 | } 559 | 560 | // Returns the ISO 3166-1 alpha-3 code for the feature matching the arguments, if any 561 | export function iso1A3Code( 562 | query: Location | string | number, 563 | opts: CodingOptions = defaultOpts 564 | ): string | null { 565 | opts.withProp = 'iso1A3'; 566 | let match = feature(query, opts); 567 | if (!match) return null; 568 | return match.properties.iso1A3 || null; 569 | } 570 | 571 | // Returns the ISO 3166-1 numeric-3 code for the feature matching the arguments, if any 572 | export function iso1N3Code( 573 | query: Location | string | number, 574 | opts: CodingOptions = defaultOpts 575 | ): string | null { 576 | opts.withProp = 'iso1N3'; 577 | let match = feature(query, opts); 578 | if (!match) return null; 579 | return match.properties.iso1N3 || null; 580 | } 581 | 582 | // Returns the UN M49 code for the feature matching the arguments, if any 583 | export function m49Code( 584 | query: Location | string | number, 585 | opts: CodingOptions = defaultOpts 586 | ): string | null { 587 | opts.withProp = 'm49'; 588 | let match = feature(query, opts); 589 | if (!match) return null; 590 | return match.properties.m49 || null; 591 | } 592 | 593 | // Returns the Wikidata QID code for the feature matching the arguments, if any 594 | export function wikidataQID( 595 | query: Location | string | number, 596 | opts: CodingOptions = defaultOpts 597 | ): string | null { 598 | opts.withProp = 'wikidata'; 599 | let match = feature(query, opts); 600 | if (!match) return null; 601 | return match.properties.wikidata; 602 | } 603 | 604 | // Returns the emoji emojiFlag sequence for the feature matching the arguments, if any 605 | export function emojiFlag( 606 | query: Location | string | number, 607 | opts: CodingOptions = defaultOpts 608 | ): string | null { 609 | opts.withProp = 'emojiFlag'; 610 | let match = feature(query, opts); 611 | if (!match) return null; 612 | return match.properties.emojiFlag || null; 613 | } 614 | 615 | // Returns the ccTLD (country code top-level domain) for the feature matching the arguments, if any 616 | export function ccTLD( 617 | query: Location | string | number, 618 | opts: CodingOptions = defaultOpts 619 | ): string | null { 620 | opts.withProp = 'ccTLD'; 621 | let match = feature(query, opts); 622 | if (!match) return null; 623 | return match.properties.ccTLD || null; 624 | } 625 | 626 | function propertiesForQuery(query: Location | Bbox, property: string): Array { 627 | let features = featuresContaining(query, false); 628 | return features 629 | .map(function (feature) { 630 | return feature.properties[property]; 631 | }) 632 | .filter(Boolean); 633 | } 634 | 635 | // Returns all the ISO 3166-1 alpha-2 codes of features at the location 636 | export function iso1A2Codes(query: Location | Bbox): Array { 637 | return propertiesForQuery(query, 'iso1A2'); 638 | } 639 | 640 | // Returns all the ISO 3166-1 alpha-3 codes of features at the location 641 | export function iso1A3Codes(query: Location | Bbox): Array { 642 | return propertiesForQuery(query, 'iso1A3'); 643 | } 644 | 645 | // Returns all the ISO 3166-1 numeric-3 codes of features at the location 646 | export function iso1N3Codes(query: Location | Bbox): Array { 647 | return propertiesForQuery(query, 'iso1N3'); 648 | } 649 | 650 | // Returns all the UN M49 codes of features at the location 651 | export function m49Codes(query: Location | Bbox): Array { 652 | return propertiesForQuery(query, 'm49'); 653 | } 654 | 655 | // Returns all the Wikidata QIDs of features at the location 656 | export function wikidataQIDs(query: Location | Bbox): Array { 657 | return propertiesForQuery(query, 'wikidata'); 658 | } 659 | 660 | // Returns all the emoji flag sequences of features at the location 661 | export function emojiFlags(query: Location | Bbox): Array { 662 | return propertiesForQuery(query, 'emojiFlag'); 663 | } 664 | 665 | // Returns all the ccTLD (country code top-level domain) sequences of features at the location 666 | export function ccTLDs(query: Location | Bbox): Array { 667 | return propertiesForQuery(query, 'ccTLD'); 668 | } 669 | 670 | // Returns the feature matching `query` and all features containing it, if any. 671 | // If passing `true` for `strict`, an exact match will not be included 672 | export function featuresContaining( 673 | query: Location | Bbox | string | number, 674 | strict?: boolean 675 | ): Array { 676 | let matchingFeatures: Array; 677 | 678 | if (Array.isArray(query) && query.length === 4) { 679 | // check if bounding box 680 | matchingFeatures = smallestFeaturesForBbox(query); 681 | } else { 682 | let smallestOrMatching = smallestOrMatchingFeature(query); 683 | matchingFeatures = smallestOrMatching ? [smallestOrMatching] : []; 684 | } 685 | 686 | if (!matchingFeatures.length) return []; 687 | 688 | let returnFeatures: Array; 689 | 690 | if (!strict || typeof query === 'object') { 691 | returnFeatures = matchingFeatures.slice(); 692 | } else { 693 | returnFeatures = []; 694 | } 695 | 696 | for (let j in matchingFeatures) { 697 | let properties = matchingFeatures[j].properties; 698 | for (let i in properties.groups) { 699 | let groupID = properties.groups[i]; 700 | let groupFeature = featuresByCode[groupID]; 701 | if (returnFeatures.indexOf(groupFeature) === -1) { 702 | returnFeatures.push(groupFeature); 703 | } 704 | } 705 | } 706 | return returnFeatures; 707 | } 708 | 709 | // Returns the feature matching `id` and all features it contains, if any. 710 | // If passing `true` for `strict`, an exact match will not be included 711 | export function featuresIn(id: string | number, strict?: boolean): Array { 712 | let feature = featureForID(id); 713 | if (!feature) return []; 714 | 715 | let features: Array = []; 716 | 717 | if (!strict) { 718 | features.push(feature); 719 | } 720 | 721 | let properties = feature.properties; 722 | if (properties.members) { 723 | for (let i in properties.members) { 724 | let memberID = properties.members[i]; 725 | features.push(featuresByCode[memberID]); 726 | } 727 | } 728 | return features; 729 | } 730 | 731 | // Returns a new feature with the properties of the feature matching `id` 732 | // and the combined geometry of all its component features 733 | export function aggregateFeature(id: string | number): RegionFeature | null { 734 | let features = featuresIn(id, false); 735 | if (features.length === 0) return null; 736 | 737 | let aggregateCoordinates = []; 738 | for (let i in features) { 739 | let feature = features[i]; 740 | if ( 741 | feature.geometry && 742 | feature.geometry.type === 'MultiPolygon' && 743 | feature.geometry.coordinates 744 | ) { 745 | aggregateCoordinates = aggregateCoordinates.concat(feature.geometry.coordinates); 746 | } 747 | } 748 | 749 | return { 750 | type: 'Feature', 751 | properties: features[0].properties, 752 | geometry: { 753 | type: 'MultiPolygon', 754 | coordinates: aggregateCoordinates 755 | } 756 | }; 757 | } 758 | 759 | // Returns true if the feature matching `query` is, or is a part of, the feature matching `bounds` 760 | export function isIn(query: Location | string | number, bounds: string | number): boolean | null { 761 | let queryFeature = smallestOrMatchingFeature(query); 762 | let boundsFeature = featureForID(bounds); 763 | 764 | if (!queryFeature || !boundsFeature) return null; 765 | 766 | if (queryFeature.properties.id === boundsFeature.properties.id) return true; 767 | return queryFeature.properties.groups.indexOf(boundsFeature.properties.id) !== -1; 768 | } 769 | 770 | // Returns true if the feature matching `query` is within EU jurisdiction 771 | export function isInEuropeanUnion(query: Location | string | number): boolean | null { 772 | return isIn(query, 'EU'); 773 | } 774 | 775 | // Returns true if the feature matching `query` is, or is within, a United Nations member state 776 | export function isInUnitedNations(query: Location | string | number): boolean | null { 777 | return isIn(query, 'UN'); 778 | } 779 | 780 | // Returns the side traffic drives on in the feature matching `query` as a string (`right` or `left`) 781 | export function driveSide(query: Location | string | number): string | null { 782 | let feature = smallestOrMatchingFeature(query); 783 | return (feature && feature.properties.driveSide) || null; 784 | } 785 | 786 | // Returns the road speed unit for the feature matching `query` as a string (`mph` or `km/h`) 787 | export function roadSpeedUnit(query: Location | string | number): string | null { 788 | let feature = smallestOrMatchingFeature(query); 789 | return (feature && feature.properties.roadSpeedUnit) || null; 790 | } 791 | 792 | // Returns the road vehicle height restriction unit for the feature matching `query` as a string (`ft` or `m`) 793 | export function roadHeightUnit(query: Location | string | number): string | null { 794 | let feature = smallestOrMatchingFeature(query); 795 | return (feature && feature.properties.roadHeightUnit) || null; 796 | } 797 | 798 | // Returns the full international calling codes for phone numbers in the feature matching `query`, if any 799 | export function callingCodes(query: Location | string | number): Array { 800 | let feature = smallestOrMatchingFeature(query); 801 | return (feature && feature.properties.callingCodes) || []; 802 | } 803 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![build](https://github.com/ideditor/country-coder/workflows/build/badge.svg)](https://github.com/ideditor/country-coder/actions?query=workflow%3A%22build%22) 2 | [![npm version](https://badge.fury.io/js/%40ideditor%2Fcountry-coder.svg)](https://badge.fury.io/js/%40ideditor%2Fcountry-coder) 3 | 4 | # country-coder 5 | 6 | 📍 ➡️ 🇩🇰 Convert longitude-latitude pairs to [ISO 3166-1 codes](https://en.wikipedia.org/wiki/ISO_3166-1) quickly and locally 7 | 8 | 9 | ## What is it? 10 | 11 | `country-coder` is a lightweight package that looks up region identifiers for geographic points without calling a server. It can code and convert between several common IDs: 12 | 13 | - 🆎 [ISO 3166-1 alpha-2 code](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) (`ZA`) 14 | - 🔤 [ISO 3166-1 alpha-3 code](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3) (`ZAF`) 15 | - 3️⃣ [ISO 3166-1 numeric-3 code](https://en.wikipedia.org/wiki/ISO_3166-1_numeric) (`710`) 16 | - 3️⃣ [United Nations M49 code](https://en.wikipedia.org/wiki/UN_M49) (`710`) 17 | - 🌐 [Wikidata QID](https://www.wikidata.org/wiki/Q43649390) (`Q258`) 18 | - 🇺🇳 [Emoji flag](https://en.wikipedia.org/wiki/Regional_Indicator_Symbol) (🇿🇦) 19 | - 💻 [ccTLD (country code top-level domain)](https://en.wikipedia.org/wiki/Country_code_top-level_domain) (`.za`) 20 | 21 | Results can optionally include non-country ISO 3166-1 features, such as Puerto Rico (`PR`) or the Isle of Man (`IM`). Some unofficial yet exceptionally-reserved or user-assigned ISO codes are also supported, such as the European Union (`EU`) and Kosovo (`XK`), as well as M49 regions like Africa (`002`) or Polynesia (`061`). 22 | 23 | In addition to identifiers, `country-coder` can provide basic regional information: 24 | 25 | - ☎️ [Telephone Calling Codes](https://en.wikipedia.org/wiki/List_of_country_calling_codes) (+44) 26 | - 🛣 [Driving Side](https://en.wikipedia.org/wiki/Left-_and_right-hand_traffic) (right, left) 27 | - 🚗 [Traffic Speed Unit](https://en.wikipedia.org/wiki/Speed_limit#Signage) (km/h, mph) 28 | - 🚚 [Vehicle Height Unit](https://wiki.openstreetmap.org/wiki/Key:maxheight) (m, ft) 29 | - 🇪🇺 [European Union Membership](https://en.wikipedia.org/wiki/Member_state_of_the_European_Union) 30 | 31 | 32 | #### Advantages 33 | 34 | Client-side coding has a number of benefits over server-side solutions: 35 | 36 | - ✅ 🚅 *Performance*: get fast, reliable results at scale 37 | - ✅ ✌️ *Ease of Use*: forget async callbacks, network errors, API keys, and rate limits 38 | - ✅ 🕶 *Privacy*: keep your location data on-device 39 | - ✅ 📴 *Offline Workflows*: deploy to connection-challenged environments 40 | 41 | #### Caveats 42 | 43 | `country-coder` prioritizes package size and lookup speed over precision. Thus, it's **not** suitable for some situations and use cases: 44 | 45 | - 🚫 🛂 *Disputed Borders*: only one country is coded per point, roughly the "de facto controlling country" 46 | - 🚫 🚢 *Maritime Borders*: only points on land are supported; borders over water are highly generalized 47 | - 🚫 🇦🇶 *Antarctic Borders*: [territorial claims in Antarctica](https://en.wikipedia.org/wiki/Territorial_claims_in_Antarctica) aren't widely recognized and are excluded 48 | - 🚫 🖋 *Complex Borders*: land borders are of varying detail and may be imprecise at granular scales 49 | - 🚫 🧩 *Country Subdivisions*: most provinces and similar features under [ISO 3166-2](https://en.wikipedia.org/wiki/ISO_3166-2) cannot be coded 50 | - 🚫 📇 *Multilingual Naming*: only basic English names are included; get display names via another package or the [Wikidata API](https://www.wikidata.org/wiki/Special:ApiSandbox#action=wbgetentities&format=json&ids=Q258&sites=&props=labels) 51 | - 🚫 📐 *Spatial Operations*: a feature's calculated area, bounding box, etc. will likely be inaccurate 52 | - 🚫 🗺 *Mapmaking*: the border data is not intended for rendering 53 | 54 | 55 | ## Installing 56 | 57 | ### Use in Node 58 | 59 | `npm install @ideditor/country-coder` 60 | 61 | **country-coder** is distributed in CJS and ESM module formats for maxmimum compatibility. ([Read more about Javascript module formats](https://dev.to/iggredible/what-the-heck-are-cjs-amd-umd-and-esm-ikm)) 62 | 63 | 64 | ```js 65 | const countryCoder = require('@ideditor/country-coder'); // CommonJS import all 66 | const iso1A2Code = require('@ideditor/country-coder').iso1A2Code; // CommonJS import named 67 | // or 68 | import * as countryCoder from '@ideditor/country-coder'; // ESM import all 69 | import { iso1A2Code } from '@ideditor/country-coder'; // ESM import named 70 | ``` 71 | 72 | 73 | ### Use in Browsers 74 | 75 | You can also use **country-coder** directly in a web browser. A good way to do this is to fetch the ["iife"](https://esbuild.github.io/api/#format-iife) bundle from the [jsDelivr CDN](https://www.jsdelivr.com/), which can even deliver minified versions. 76 | 77 | When you load this file in a ` 81 | 82 | … 83 | 86 | ``` 87 | 88 | 👉 This project uses modern JavaScript syntax for use in supported node versions and modern browsers. If you need support for legacy environments like ES5 or Internet Explorer, you'll need to build your own bundle with something like [Babel](https://babeljs.io/docs/en/index.html). 89 | 90 | 91 | ## Quick Start 92 | 93 | Simply pass in a `[longitude, latitude]` to `iso1A2Code` to get the country code. 94 | 95 | ```js 96 | iso1A2Code([-4.5, 54.2]); // returns 'GB' 97 | ``` 98 | 99 | To include non-country territories, pass in `territory` for the `level` option. 100 | 101 | ```js 102 | iso1A2Code([-4.5, 54.2], { level: 'territory' }); // returns 'IM' 103 | ``` 104 | 105 | The same method can convert from other identifiers. 106 | 107 | ```js 108 | iso1A2Code('Q145'); // returns 'GB' 109 | ``` 110 | 111 | Read the [full API reference](#api-reference) to see everything `country-coder` can do. 112 | 113 | 114 | ## Contributing 115 | 116 | This package is kept intentionally minimal. However, if you find a bug or have an interesting idea for an enhancement, feel free to open an [Issue](https://github.com/ideditor/country-coder/issues) and/or [Pull Request](https://github.com/ideditor/country-coder/pulls). 117 | 118 | 119 | ## API Reference 120 | 121 | ##### Methods 122 | * [feature](#feature)(query: Location | string | number, opts?: CodingOptions): RegionFeature? 123 | * [iso1A2Code](#iso1A2Code)(query: Location | string | number, opts?: CodingOptions): string? 124 | * [iso1A2Codes](#iso1A2Codes)(query: Location | Bbox): [string] 125 | * [iso1A3Code](#iso1A3Code)(query: Location | string | number, opts?: CodingOptions): string? 126 | * [iso1A3Codes](#iso1A3Codes)(query: Location | Bbox): [string] 127 | * [iso1N3Code](#iso1N3Code)(query: Location | string | number, opts?: CodingOptions): string? 128 | * [iso1N3Codes](#iso1N3Codes)(query: Location | Bbox): [string] 129 | * [m49Code](#m49Code)(query: Location | string | number, opts?: CodingOptions): string? 130 | * [m49Codes](#m49Codes)(query: Location | Bbox): [string] 131 | * [wikidataQID](#wikidataQID)(query: Location | string | number, opts?: CodingOptions): string? 132 | * [wikidataQIDs](#wikidataQIDs)(query: Location | Bbox): [string] 133 | * [emojiFlag](#emojiFlag)(query: Location | string | number, opts?: CodingOptions): string? 134 | * [emojiFlags](#emojiFlags)(query: Location | Bbox): [string] 135 | * [ccTLD](#ccTLD)(query: Location | string | number, opts?: CodingOptions): string? 136 | * [ccTLDs](#ccTLDs)(query: Location | Bbox): [string] 137 | * [featuresContaining](#featuresContaining)(query: Location | Bbox | string | number, strict: boolean): [RegionFeature] 138 | * [featuresIn](#featuresIn)(id: string | number, strict: boolean): [RegionFeature] 139 | * [aggregateFeature](#aggregateFeature)(id: string | number): [RegionFeature] 140 | * [isIn](#isIn)(query: Location | string | number, bounds: string | number): boolean 141 | * [isInEuropeanUnion](#isInEuropeanUnion)(query: Location | string | number): boolean 142 | * [isInUnitedNations](#isInUnitedNations)(query: Location | string | number): boolean 143 | * [driveSide](#driveSide)(query: Location | string | number): string? 144 | * [roadSpeedUnit](#roadSpeedUnit)(query: Location | string | number): string? 145 | * [roadHeightUnit](#roadHeightUnit)(query: Location | string | number): string? 146 | * [callingCodes](#callingCodes)(query: Location | string | number): [string] 147 | 148 | ##### Properties 149 | * [borders](#borders): RegionFeatureCollection - the base GeoJSON containing all features 150 | 151 | ##### Types 152 | * [Vec2](#Vec2): [number, number] 153 | * [Bbox](#Bbox): [number, number, number, number] 154 | * [PointGeometry](#PointGeometry): a GeoJSON Point geometry object 155 | * [PointFeature](#PointFeature): a GeoJSON feature object with a Point geometry type 156 | * [Location](#Location): Vec2 | PointGeometry | PointFeature 157 | * [CodingOptions](#CodingOptions) 158 | * [RegionFeature](#RegionFeature) 159 | * [RegionFeatureProperties](#RegionFeatureProperties) 160 | * [RegionFeatureCollection](#RegionFeatureCollection) 161 | 162 | 163 | ## Methods 164 | 165 | # feature(query: Location | string | number, opts?: CodingOptions): RegionFeature? 166 | 167 | Returns the GeoJSON feature from `borders` for the given location or identifier and options, if found. Note that the `geometry` of the feature may not contain its full bounds (see [aggregateFeature](#aggregateFeature)). 168 | 169 | ```js 170 | feature([-4.5, 54.2]); // returns United Kingdom feature 171 | feature([-4.5, 54.2], { level: 'territory' }); // returns {Isle of Man} 172 | feature([0, 90]); // returns null 173 | feature('GB'); // returns {United Kingdom} 174 | feature('GBR'); // returns {United Kingdom} 175 | feature('826'); // returns {United Kingdom} 176 | feature(826); // returns {United Kingdom} 177 | feature('Q145'); // returns {United Kingdom} 178 | feature('🇬🇧'); // returns {United Kingdom} 179 | feature('.uk'); // returns {United Kingdom} 180 | feature('UK'); // returns {United Kingdom} 181 | feature('IM'); // returns {Isle of Man} 182 | feature('United Kingdom'); // returns {United Kingdom} 183 | 184 | let pointGeoJSON = { type: 'Feature', geometry: { type: 'Point', coordinates: [-4.5, 54.2] } }; 185 | feature(pointGeoJSON); // returns {United Kingdom} 186 | feature(pointGeoJSON.geometry); // returns {United Kingdom} 187 | ``` 188 | 189 | 190 | # iso1A2Code(query: Location | string | number, opts?: CodingOptions): string? 191 | 192 | Returns the ISO 3166-1 alpha-2 code for the given location or identifier and options, if found. 193 | 194 | ```js 195 | iso1A2Code([-4.5, 54.2]); // returns 'GB' 196 | iso1A2Code([-4.5, 54.2], { level: 'territory' }); // returns 'IM' 197 | iso1A2Code([0, 90]); // returns null 198 | iso1A2Code('GBR'); // returns 'GB' 199 | iso1A2Code('826'); // returns 'GB' 200 | iso1A2Code(826); // returns 'GB' 201 | iso1A2Code('Q145'); // returns 'GB' 202 | iso1A2Code('🇬🇧'); // returns 'GB' 203 | iso1A2Code('.uk'); // returns 'GB' 204 | iso1A2Code('UK'); // returns 'GB' 205 | iso1A2Code('IMN'); // returns 'IM' 206 | iso1A2Code('United Kingdom'); // returns 'GB' 207 | 208 | let pointGeoJSON = { type: 'Feature', geometry: { type: 'Point', coordinates: [-4.5, 54.2] } }; 209 | iso1A2Code(pointGeoJSON); // returns 'GB' 210 | iso1A2Code(pointGeoJSON.geometry); // returns 'GB' 211 | ``` 212 | 213 | 214 | # iso1A2Codes(query: Location | Bbox): [string] 215 | 216 | Returns all the ISO 3166-1 alpha-2 codes for the given location or bounding box, if any. 217 | 218 | ```js 219 | iso1A2Codes([-4.5, 54.2]); // returns ['IM', 'GB', 'UN'] 220 | iso1A2Codes([0, 90]); // returns [] 221 | 222 | let pointGeoJSON = { type: 'Feature', geometry: { type: 'Point', coordinates: [-4.5, 54.2] } }; 223 | iso1A2Codes(pointGeoJSON); // returns ['IM', 'GB', 'UN'] 224 | iso1A2Codes(pointGeoJSON.geometry); // returns ['IM', 'GB', 'UN'] 225 | ``` 226 | 227 | 228 | # iso1A3Code(query: Location | string | number, opts?: CodingOptions): string? 229 | 230 | Returns the ISO 3166-1 alpha-3 code for the given location or identifier and options, if found. 231 | 232 | ```js 233 | iso1A3Code([-4.5, 54.2]); // returns 'GBR' 234 | iso1A3Code([-4.5, 54.2], { level: 'territory' }); // returns 'IMN' 235 | iso1A3Code([0, 90]); // returns null 236 | iso1A3Code('GB'); // returns 'GBR' 237 | iso1A3Code('826'); // returns 'GBR' 238 | iso1A3Code(826); // returns 'GBR' 239 | iso1A3Code('Q145'); // returns 'GBR' 240 | iso1A3Code('🇬🇧'); // returns 'GBR' 241 | iso1A3Code('.uk'); // returns 'GBR' 242 | iso1A3Code('UK'); // returns 'GBR' 243 | iso1A3Code('IM'); // returns 'IMN' 244 | iso1A3Code('United Kingdom'); // returns 'GBR' 245 | 246 | let pointGeoJSON = { type: 'Feature', geometry: { type: 'Point', coordinates: [-4.5, 54.2] } }; 247 | iso1A3Code(pointGeoJSON); // returns 'GBR' 248 | iso1A3Code(pointGeoJSON.geometry); // returns 'GBR' 249 | ``` 250 | 251 | 252 | # iso1A3Codes(query: Location | Bbox): [string] 253 | 254 | Returns all the ISO 3166-1 alpha-3 codes for the given location of bounding box, if any. 255 | 256 | ```js 257 | iso1A3Codes([-4.5, 54.2]); // returns ['IMN', 'GBR'] 258 | iso1A3Codes([0, 90]); // returns [] 259 | 260 | let pointGeoJSON = { type: 'Feature', geometry: { type: 'Point', coordinates: [-4.5, 54.2] } }; 261 | iso1A3Codes(pointGeoJSON); // returns ['IMN', 'GBR'] 262 | iso1A3Codes(pointGeoJSON.geometry); // returns ['IMN', 'GBR'] 263 | ``` 264 | 265 | 266 | # iso1N3Code(query: Location | string | number, opts?: CodingOptions): string? 267 | 268 | Returns the ISO 3166-1 numeric-3 code for the given location or identifier and options, if found. For more comprehensive coverage, see [m49Code](#m49Code). 269 | 270 | ```js 271 | iso1N3Code([-4.5, 54.2]); // returns '826' 272 | iso1N3Code([-4.5, 54.2], { level: 'territory' }); // returns '833' 273 | iso1N3Code([0, 90]); // returns null 274 | iso1N3Code('GB'); // returns '826' 275 | iso1N3Code('GBR'); // returns '826' 276 | iso1N3Code('Q145'); // returns '826' 277 | iso1N3Code('🇬🇧'); // returns '826' 278 | iso1N3Code('.uk'); // returns '826' 279 | iso1N3Code('UK'); // returns '826' 280 | iso1N3Code('IM'); // returns '833' 281 | iso1N3Code('Q15'); // returns null (Africa) 282 | iso1A3Code('United Kingdom'); // returns '826' 283 | 284 | let pointGeoJSON = { type: 'Feature', geometry: { type: 'Point', coordinates: [-4.5, 54.2] } }; 285 | iso1N3Code(pointGeoJSON); // returns '826' 286 | iso1N3Code(pointGeoJSON.geometry); // returns '826' 287 | ``` 288 | 289 | 290 | # iso1N3Codes(query: Location | Bbox): [string] 291 | 292 | Returns all the ISO 3166-1 numeric-3 codes for the given location or bounding box, if any. 293 | 294 | ```js 295 | iso1N3Codes([-4.5, 54.2]); // returns ['833', '826'] 296 | iso1N3Codes([0, 90]); // returns [] 297 | 298 | let pointGeoJSON = { type: 'Feature', geometry: { type: 'Point', coordinates: [-4.5, 54.2] } }; 299 | iso1N3Codes(pointGeoJSON); // returns ['833', '826'] 300 | iso1N3Codes(pointGeoJSON.geometry); // returns ['833', '826'] 301 | ``` 302 | 303 | 304 | # m49Code(query: Location | string | number, opts?: CodingOptions): string? 305 | 306 | Returns the United Nations M49 code for the given location or identifier and options, if found. These codes are a superset of ISO 3166-1 numeric-3 codes, adding a subdivision (Sark) and transnational regions (e.g. Asia, Central America, Polynesia). 307 | 308 | ```js 309 | m49Code([-4.5, 54.2]); // returns '826' 310 | m49Code([-4.5, 54.2], { level: 'territory' }); // returns '833' 311 | m49Code([0, 90]); // returns null 312 | m49Code('GB'); // returns '826' 313 | m49Code('GBR'); // returns '826' 314 | m49Code('Q145'); // returns '826' 315 | m49Code('🇬🇧'); // returns '826' 316 | m49Code('.uk'); // returns '826' 317 | m49Code('UK'); // returns '826' 318 | m49Code('IM'); // returns '833' 319 | m49Code('Q15'); // returns '002' (Africa) 320 | m49Code('United Kingdom'); // returns '826' 321 | 322 | let pointGeoJSON = { type: 'Feature', geometry: { type: 'Point', coordinates: [-4.5, 54.2] } }; 323 | m49Code(pointGeoJSON); // returns '826' 324 | m49Code(pointGeoJSON.geometry); // returns '826' 325 | ``` 326 | 327 | 328 | # m49Codes(query: Location | Bbox): [string] 329 | 330 | Returns all the United Nations M49 codes for the given location or bounding box, if any. 331 | 332 | ```js 333 | m49Codes([-4.5, 54.2]); // returns ['833', '826', '154', '150', '001'] 334 | m49Codes([0, 90]); // returns [] 335 | 336 | let pointGeoJSON = { type: 'Feature', geometry: { type: 'Point', coordinates: [-4.5, 54.2] } }; 337 | m49Codes(pointGeoJSON); // returns ['833', '826', '154', '150', '001'] 338 | m49Codes(pointGeoJSON.geometry); // returns ['833', '826', '154', '150', '001'] 339 | ``` 340 | 341 | 342 | # wikidataQID(query: Location | string | number, opts?: CodingOptions): string? 343 | 344 | Returns the Wikidata QID for the given location or identifier and options, if found. 345 | 346 | ```js 347 | wikidataQID([-4.5, 54.2]); // returns 'Q145' 348 | wikidataQID([-4.5, 54.2], { level: 'territory' }); // returns 'Q9676' 349 | wikidataQID([0, 90]); // returns null 350 | wikidataQID('GB'); // returns 'Q145' 351 | wikidataQID('GBR'); // returns 'Q145' 352 | wikidataQID('826'); // returns 'Q145' 353 | wikidataQID(826); // returns 'Q145' 354 | wikidataQID('🇬🇧'); // returns 'Q145' 355 | wikidataQID('.uk'); // returns 'Q145' 356 | wikidataQID('UK'); // returns 'Q145' 357 | wikidataQID('IM'); // returns 'Q9676' 358 | wikidataQID('United Kingdom'); // returns 'Q145' 359 | 360 | let pointGeoJSON = { type: 'Feature', geometry: { type: 'Point', coordinates: [-4.5, 54.2] } }; 361 | wikidataQID(pointGeoJSON); // returns 'Q145' 362 | wikidataQID(pointGeoJSON.geometry); // returns 'Q145' 363 | ``` 364 | 365 | 366 | # wikidataQIDs(query: Location | Bbox): [string] 367 | 368 | Returns all the Wikidata QIDs for the given location or bounding box, if any. 369 | 370 | ```js 371 | wikidataQIDs([-4.5, 54.2]); // returns ['Q9676', 'Q185086', 'Q145', 'Q27479', 'Q46', 'Q1065', 'Q2'] 372 | wikidataQIDs([0, 90]); // returns [] 373 | 374 | let pointGeoJSON = { type: 'Feature', geometry: { type: 'Point', coordinates: [-4.5, 54.2] } }; 375 | wikidataQIDs(pointGeoJSON); // returns ['Q9676', 'Q185086', 'Q145', 'Q27479', 'Q46', 'Q1065', 'Q2'] 376 | wikidataQIDs(pointGeoJSON.geometry); // returns ['Q9676', 'Q185086', 'Q145', 'Q27479', 'Q46', 'Q1065', 'Q2'] 377 | ``` 378 | 379 | 380 | # emojiFlag(query: Location | string | number, opts?: CodingOptions): string? 381 | 382 | Returns the emoji flag sequence for the given location or identifier and options, if found. 383 | 384 | ```js 385 | emojiFlag([-4.5, 54.2]); // returns '🇬🇧' 386 | emojiFlag([-4.5, 54.2], { level: 'territory' }); // returns '🇮🇲' 387 | emojiFlag([0, 90]); // returns null 388 | emojiFlag('GB'); // returns '🇬🇧' 389 | emojiFlag('GBR'); // returns '🇬🇧' 390 | emojiFlag('826'); // returns '🇬🇧' 391 | emojiFlag(826); // returns '🇬🇧' 392 | emojiFlag('Q145'); // returns '🇬🇧' 393 | emojiFlag('UK'); // returns '🇬🇧' 394 | emojiFlag('IM'); // returns '🇮🇲' 395 | emojiFlag('United Kingdom'); // returns '🇬🇧' 396 | 397 | let pointGeoJSON = { type: 'Feature', geometry: { type: 'Point', coordinates: [-4.5, 54.2] } }; 398 | emojiFlag(pointGeoJSON); // returns '🇬🇧' 399 | emojiFlag(pointGeoJSON.geometry); // returns '🇬🇧' 400 | ``` 401 | 402 | 403 | # emojiFlags(query: Location | Bbox): [string] 404 | 405 | Returns all the emoji flag sequences for the given location or bounding box, if any. 406 | 407 | ```js 408 | emojiFlags([-4.5, 54.2]); // returns ['🇮🇲', '🇬🇧', '🇺🇳'] 409 | emojiFlags([0, 90]); // returns [] 410 | 411 | let pointGeoJSON = { type: 'Feature', geometry: { type: 'Point', coordinates: [-4.5, 54.2] } }; 412 | emojiFlags(pointGeoJSON); // returns ['🇮🇲', '🇬🇧', '🇺🇳'] 413 | emojiFlags(pointGeoJSON.geometry); // returns ['🇮🇲', '🇬🇧', '🇺🇳'] 414 | ``` 415 | 416 | 417 | # ccTLD(query: Location | string | number, opts?: CodingOptions): string? 418 | 419 | Returns the country code top-level internet domain for the given location or identifier and options, if found. 420 | 421 | ```js 422 | ccTLD([-4.5, 54.2]); // returns '.uk' 423 | ccTLD([-4.5, 54.2], { level: 'territory' }); // returns '.im' 424 | ccTLD([0, 90]); // returns null 425 | ccTLD('GB'); // returns '.uk' 426 | ccTLD('GBR'); // returns '.uk' 427 | ccTLD('826'); // returns '.uk' 428 | ccTLD(826); // returns '.uk' 429 | ccTLD('Q145'); // returns '.uk' 430 | ccTLD('UK'); // returns '.uk' 431 | ccTLD('IM'); // returns '.im' 432 | ccTLD('United Kingdom'); // returns '.uk' 433 | 434 | let pointGeoJSON = { type: 'Feature', geometry: { type: 'Point', coordinates: [-4.5, 54.2] } }; 435 | ccTLD(pointGeoJSON); // returns '.uk' 436 | ccTLD(pointGeoJSON.geometry); // returns '.uk' 437 | ``` 438 | 439 | 440 | # ccTLDs(query: Location | Bbox): [string] 441 | 442 | Returns all the country code top-level internet domains for the given location or bounding box, if any. 443 | 444 | ```js 445 | ccTLDs([-4.5, 54.2]); // returns ['.im', '.uk'] 446 | ccTLDs([0, 90]); // returns [] 447 | 448 | let pointGeoJSON = { type: 'Feature', geometry: { type: 'Point', coordinates: [-4.5, 54.2] } }; 449 | ccTLDs(pointGeoJSON); // returns ['.im', '.uk'] 450 | ccTLDs(pointGeoJSON.geometry); // returns ['.im', '.uk'] 451 | ``` 452 | 453 | 454 | # featuresContaining(query: Location | Bbox | string | number, strict: boolean): [RegionFeature] 455 | 456 | Returns all the the features of any type that contain or match the given location, bounding box, or identifier, if any. If `strict` is `true` and `query` is an identifier, then only features that are strictly containing are returned. 457 | 458 | ```js 459 | featuresContaining([-4.5, 54.2]); // returns [{Isle of Man}, {Crown Dependencies}, {United Kingdom}, {Northern Europe}, {Europe}, {United Nations}, {World}] 460 | featuresContaining([0, 51.5]); // returns [{England}, {Countries of the United Kingdom}, {United Kingdom}, {Great Britain}, {Northern Europe}, {Europe}, {United Nations}, {World}] 461 | featuresContaining([6.1, 46.2]); // returns [{Switzerland}, {Western Europe}, {Europe}, {United Nations}, {World}] 462 | featuresContaining([0, 90]); // returns [] 463 | featuresContaining('GB'); // returns [{United Kingdom}, {United Nations}, {World}] 464 | featuresContaining('GBR'); // returns [{United Kingdom}, {United Nations}, {World}] 465 | featuresContaining('826'); // returns [{United Kingdom}, {United Nations}, {World}] 466 | featuresContaining(826); // returns [{United Kingdom}, {United Nations}, {World}] 467 | featuresContaining('Q145'); // returns [{United Kingdom}, {United Nations}, {World}] 468 | featuresContaining('🇬🇧'); // returns [{United Kingdom}, {United Nations}, {World}] 469 | featuresContaining('.uk'); // returns [{United Kingdom}, {United Nations}, {World}] 470 | featuresContaining('UK'); // returns [{United Kingdom}, {United Nations}, {World}] 471 | featuresContaining('154'); // returns [{Northern Europe}, {Europe}, {World}] 472 | featuresContaining('GB', true); // returns [{United Nations}, {World}] 473 | featuresContaining('154', true); // returns [{Europe}, {World}] 474 | 475 | let pointGeoJSON = { type: 'Feature', geometry: { type: 'Point', coordinates: [0, -90] } }; 476 | featuresContaining(pointGeoJSON); // returns [{Antarctica}] 477 | featuresContaining(pointGeoJSON.geometry); // returns [{Antarctica}] 478 | ``` 479 | 480 | 481 | # featuresIn(id: string | number, strict: boolean): [RegionFeature] 482 | 483 | Returns all the the features that match or are contained within the given identifier, if any. If `strict` is `true` then only features that are strictly contained are returned. 484 | 485 | ```js 486 | featuresIn('CN'); // returns [{China}, {Mainland China}, {Hong Kong}, {Macau}] 487 | featuresIn('CHN'); // returns [{China}, {Mainland China}, {Hong Kong}, {Macau}] 488 | featuresIn('156'); // returns [{China}, {Mainland China}, {Hong Kong}, {Macau}] 489 | featuresIn(156); // returns [{China}, {Mainland China}, {Hong Kong}, {Macau}] 490 | featuresIn('Q148'); // returns [{China}, {Mainland China}, {Hong Kong}, {Macau}] 491 | featuresIn('🇨🇳'); // returns [{China}, {Mainland China}, {Hong Kong}, {Macau}] 492 | featuresIn('.cn'); // returns [{China}, {Mainland China}, {Hong Kong}, {Macau}] 493 | featuresIn('China'); // returns [{China}, {Mainland China}, {Hong Kong}, {Macau}] 494 | featuresIn('CN', true); // returns [{Mainland China}, {Hong Kong}, {Macau}] 495 | ``` 496 | 497 | 498 | # aggregateFeature(id: string | number): [RegionFeature] 499 | 500 | Returns a new feature with the `properties` of the feature matching `id` and the combined `geometry` of it and all its component features. This step is not necessary when only accessing a feature's properties. 501 | 502 | ```js 503 | aggregateFeature('CN'); // returns Mainland China, Hong Kong, and Macau as one feature 504 | aggregateFeature('CHN'); // returns Mainland China, Hong Kong, and Macau as one feature 505 | aggregateFeature('156'); // returns Mainland China, Hong Kong, and Macau as one feature 506 | aggregateFeature(156); // returns Mainland China, Hong Kong, and Macau as one feature 507 | aggregateFeature('Q148'); // returns Mainland China, Hong Kong, and Macau as one feature 508 | aggregateFeature('🇨🇳'); // returns Mainland China, Hong Kong, and Macau as one feature 509 | aggregateFeature('.cn'); // returns Mainland China, Hong Kong, and Macau as one feature 510 | aggregateFeature('China'); // returns Mainland China, Hong Kong, and Macau as one feature 511 | ``` 512 | 513 | 514 | # isIn(query: Location | string | number, bounds: string | number): boolean 515 | 516 | Returns `true` if the feature matching `query` is, or is within, the feature matching `bounds`. 517 | 518 | ```js 519 | isIn([0, 51.5], 'GB'); // returns true 520 | isIn([-4.5, 54.2], 'IM'); // returns true 521 | isIn([-4.5, 54.2], 'GB'); // returns true 522 | isIn([-4.5, 54.2], 'CH'); // returns false 523 | isIn([6.1, 46.2], 'GB'); // returns false 524 | isIn('IM', 'GB'); // returns true 525 | isIn('GB', 'IM'); // returns false 526 | isIn('GB', '150'); // returns true 527 | isIn('GBR', 150); // returns true 528 | isIn('826', 'Q46'); // returns true 529 | isIn('🇮🇲', '🇬🇧'); // returns true 530 | isIn('.im', '.uk'); // returns true 531 | isIn('United Kingdom', 'Europe'); // returns true 532 | isIn('United Kingdom', 'Africa'); // returns false 533 | 534 | let pointGeoJSON = { type: 'Feature', geometry: { type: 'Point', coordinates: [0, 51.5] } }; 535 | isIn(pointGeoJSON, 'GB'); // returns true 536 | isIn(pointGeoJSON.geometry, 'GB'); // returns true 537 | ``` 538 | 539 | 540 | # isInEuropeanUnion(query: Location | string | number): boolean 541 | 542 | Returns `true` if the feature with the given location or identifier is found to be part of the European Union. This is a convenience method for `isIn(query, 'EU')`. 543 | 544 | ```js 545 | isInEuropeanUnion([13.4, 52.5]); // returns true (Germany) 546 | isInEuropeanUnion([0, 51.5]); // returns false (Britain) 547 | isInEuropeanUnion([-4.5, 54.2]); // returns false (Isle of Man) 548 | isInEuropeanUnion([6.1, 46.2]); // returns false (Switzerland) 549 | isInEuropeanUnion([0, 90]); // returns false (North Pole) 550 | isInEuropeanUnion('EU'); // returns true 551 | isInEuropeanUnion('DE'); // returns true 552 | isInEuropeanUnion('DEU'); // returns true 553 | isInEuropeanUnion('276'); // returns true 554 | isInEuropeanUnion(276); // returns true 555 | isInEuropeanUnion('Q183'); // returns true 556 | isInEuropeanUnion('🇩🇪'); // returns true 557 | isInEuropeanUnion('.de'); // returns true 558 | isInEuropeanUnion('Germany'); // returns true 559 | isInEuropeanUnion('GB'); // returns false 560 | isInEuropeanUnion('IM'); // returns false 561 | isInEuropeanUnion('.im'); // returns false 562 | isInEuropeanUnion('CH'); // returns false 563 | 564 | 565 | let pointGeoJSON = { type: 'Feature', geometry: { type: 'Point', coordinates: [13.4, 52.5] } }; 566 | isInEuropeanUnion(pointGeoJSON); // returns true (Germany) 567 | isInEuropeanUnion(pointGeoJSON.geometry); // returns true (Germany) 568 | ``` 569 | 570 | 571 | # isInUnitedNations(query: Location | string | number): boolean 572 | 573 | Returns `true` if the feature with the given location or identifier is found to be part of a member state of the United Nations. This is a convenience method for `isIn(query, 'UN')`. 574 | 575 | ```js 576 | isInUnitedNations([13.4, 52.5]); // returns true (Germany) 577 | isInUnitedNations([0, 51.5]); // returns true (Britain) 578 | isInUnitedNations([-4.5, 54.2]); // returns true (Isle of Man) 579 | isInUnitedNations([6.1, 46.2]); // returns true (Switzerland) 580 | isInUnitedNations([0, 90]); // returns false (North Pole) 581 | isInUnitedNations('EU'); // returns true 582 | isInUnitedNations('DE'); // returns true 583 | isInUnitedNations('DEU'); // returns true 584 | isInUnitedNations('276'); // returns true 585 | isInUnitedNations(276); // returns true 586 | isInUnitedNations('Q183'); // returns true 587 | isInUnitedNations('🇩🇪'); // returns true 588 | isInUnitedNations('.de'); // returns true 589 | isInUnitedNations('Germany'); // returns true 590 | isInUnitedNations('GB'); // returns true 591 | isInUnitedNations('IM'); // returns true 592 | isInUnitedNations('.im'); // returns true 593 | isInUnitedNations('CH'); // returns true 594 | isInUnitedNations('XK'); // returns false (Kosovo) 595 | isInUnitedNations('PS'); // returns false (Palestine) 596 | 597 | 598 | let pointGeoJSON = { type: 'Feature', geometry: { type: 'Point', coordinates: [13.4, 52.5] } }; 599 | isInUnitedNations(pointGeoJSON); // returns true (Germany) 600 | isInUnitedNations(pointGeoJSON.geometry); // returns true (Germany) 601 | ``` 602 | 603 | 604 | # driveSide(query: Location | string | number): string? 605 | 606 | Returns the side of the road on which traffic drives for the given location or identifier, if found. 607 | 608 | ```js 609 | driveSide([0, 51.5]); // returns 'left' (Britain) 610 | driveSide([6.1, 46.2]); // returns 'right' (Switzerland) 611 | driveSide([0, 90]); // returns null (North Pole) 612 | driveSide('EU'); // returns null 613 | driveSide('GB'); // returns 'left' 614 | driveSide('GBR'); // returns 'left' 615 | driveSide('826'); // returns 'left' 616 | driveSide(826); // returns 'left' 617 | driveSide('Q145'); // returns 'left' 618 | driveSide('🇬🇧'); // returns 'left' 619 | driveSide('.uk'); // returns 'left' 620 | driveSide('UK'); // returns 'left' 621 | driveSide('United Kingdom'); // returns 'left' 622 | driveSide('CH'); // returns 'right' 623 | 624 | let pointGeoJSON = { type: 'Feature', geometry: { type: 'Point', coordinates: [0, 51.5] } }; 625 | driveSide(pointGeoJSON); // returns 'left' (Britain) 626 | driveSide(pointGeoJSON.geometry); // returns 'left' (Britain) 627 | ``` 628 | 629 | 630 | # roadSpeedUnit(query: Location | string | number): string? 631 | 632 | Returns the unit of speed used on traffic signs for the given location or identifier, if found. 633 | 634 | ```js 635 | roadSpeedUnit([0, 51.5]); // returns 'mph' (Britain) 636 | roadSpeedUnit([6.1, 46.2]); // returns 'km/h' (Switzerland) 637 | roadSpeedUnit([0, 90]); // returns null (North Pole) 638 | roadSpeedUnit('EU'); // returns null 639 | roadSpeedUnit('GB'); // returns 'mph' 640 | roadSpeedUnit('GBR'); // returns 'mph' 641 | roadSpeedUnit('826'); // returns 'mph' 642 | roadSpeedUnit(826); // returns 'mph' 643 | roadSpeedUnit('Q145'); // returns 'mph' 644 | roadSpeedUnit('🇬🇧'); // returns 'mph' 645 | roadSpeedUnit('.uk'); // returns 'mph' 646 | roadSpeedUnit('UK'); // returns 'mph' 647 | roadSpeedUnit('United Kingdom'); // returns 'mph' 648 | roadSpeedUnit('CH'); // returns 'km/h' 649 | 650 | let pointGeoJSON = { type: 'Feature', geometry: { type: 'Point', coordinates: [0, 51.5] } }; 651 | roadSpeedUnit(pointGeoJSON); // returns 'mph' (Britain) 652 | roadSpeedUnit(pointGeoJSON.geometry); // returns 'mph' (Britain) 653 | ``` 654 | 655 | 656 | # roadHeightUnit(query: Location | string | number): string? 657 | 658 | Returns the unit of length used on vehicle height restriction traffic signs for the given location or identifier, if found. 659 | 660 | ```js 661 | roadHeightUnit([0, 51.5]); // returns 'ft' (Britain) 662 | roadHeightUnit([6.1, 46.2]); // returns 'm' (Switzerland) 663 | roadHeightUnit([0, 90]); // returns null (North Pole) 664 | roadHeightUnit('EU'); // returns null 665 | roadHeightUnit('GB'); // returns 'ft' 666 | roadHeightUnit('GBR'); // returns 'ft' 667 | roadHeightUnit('826'); // returns 'ft' 668 | roadHeightUnit(826); // returns 'ft' 669 | roadHeightUnit('Q145'); // returns 'ft' 670 | roadHeightUnit('🇬🇧'); // returns 'ft' 671 | roadHeightUnit('.uk'); // returns 'ft' 672 | roadHeightUnit('UK'); // returns 'ft' 673 | roadHeightUnit('United Kingdom'); // returns 'ft' 674 | roadHeightUnit('CH'); // returns 'm' 675 | 676 | let pointGeoJSON = { type: 'Feature', geometry: { type: 'Point', coordinates: [0, 51.5] } }; 677 | roadHeightUnit(pointGeoJSON); // returns 'ft' (Britain) 678 | roadHeightUnit(pointGeoJSON.geometry); // returns 'ft' (Britain) 679 | ``` 680 | 681 | 682 | # callingCodes(query: Location | string | number): [string] 683 | 684 | Returns the full international calling code prefix of phone numbers for the given location or identifier, if any. All prefixes have a country code, with some also including an area code separated by a space character. These are commonly formatted with a preceding plus sign (e.g. `+1 242`). 685 | 686 | ```js 687 | callingCodes([0, 51.5]); // returns ['44'] (Britain) 688 | callingCodes([0, 90]); // returns [] (North Pole) 689 | callingCodes('EU'); // returns [] 690 | callingCodes('GB'); // returns ['44'] 691 | callingCodes('GBR'); // returns ['44'] 692 | callingCodes('826'); // returns ['44'] 693 | callingCodes(826); // returns ['44'] 694 | callingCodes('Q145'); // returns ['44'] 695 | callingCodes('🇬🇧'); // returns ['44'] 696 | callingCodes('.uk'); // returns ['44'] 697 | callingCodes('UK'); // returns ['44'] 698 | callingCodes('United Kingdom'); // returns ['44'] 699 | callingCodes('BS'); // returns ['1 242'] 700 | callingCodes('JA'); // returns ['1 876', '1 658'] 701 | 702 | let pointGeoJSON = { type: 'Feature', geometry: { type: 'Point', coordinates: [0, 51.5] } }; 703 | callingCodes(pointGeoJSON); // returns ['44'] (Britain) 704 | callingCodes(pointGeoJSON.geometry); // returns ['44'] (Britain) 705 | ``` 706 | 707 | 708 | ## Properties 709 | 710 | # borders: RegionFeatureCollection
711 | 712 | The base GeoJSON feature collection used for feature lookup. While this property is public, modifying it is not recommended and may have unintended effects. 713 | 714 | 715 | ## Types 716 | 717 | # Vec2 718 | 719 | An array of two numbers as `[longitude, latitude]` referenced to the WGS 84 datum. 720 | 721 | `[number, number]` 722 | 723 | # Bbox 724 | 725 | A bounding box represented as an array of four numbers `[minLongitude, minLatitude, maxLongitude, maxLatitude]` referenced to the WGS 84 datum. 726 | 727 | `[number, number, number, number]` 728 | 729 | 730 | # PointGeometry 731 | 732 | GeoJSON [Point geometry](https://tools.ietf.org/html/rfc7946#section-3.1.2) as specified by RFC 7946. 733 | 734 | 735 | # PointFeature 736 | 737 | A GeoJSON Feature with [Point geometry](https://tools.ietf.org/html/rfc7946#section-3.1.2) as specified by RFC 7946. 738 | 739 | 740 | # Location 741 | 742 | A geographic location in one of the supported formats. 743 | 744 | `Vec2 | PointGeometry | PointFeature` 745 | 746 | 747 | # CodingOptions 748 | 749 | An object containing options used for geocoding. 750 | 751 | - `level`: `string`, for overlapping features, the preferred geographic classification of the one to code. If no feature exists at the specified level, the feature at the next-highest level is coded, if any. For possible values, see the `level` property of [RegionFeatureProperties](#RegionFeatureProperties). 752 | - `maxLevel`: `string`, the highest-level that a returned feature may have. Must be greater than or equal to `level`. 753 | 754 | 755 | # RegionFeature 756 | 757 | A GeoJSON feature representing a codable geographic area. 758 | 759 | 760 | # RegionFeatureProperties 761 | 762 | An object containing the attributes of a RegionFeature object. 763 | 764 | - `id`: `string`, a unique ID for this feature specific to country-coder 765 | - `iso1A2`: `string`, ISO 3166-1 alpha-2 code 766 | - `iso1A3`: `string`, ISO 3166-1 alpha-3 code 767 | - `iso1N3`: `string`, ISO 3166-1 numeric-3 code 768 | - `m49`: `string`, UN M49 code 769 | - `wikidata`: `string`, Wikidata QID 770 | - `emojiFlag`: `string`, the emoji flag sequence derived from this feature's ISO 3166-1 alpha-2 code 771 | - `ccTLD`: `string`, the ccTLD (country code top-level internet domain) 772 | - `nameEn`: `string`, common name in English 773 | - `aliases`: `[string]`, additional identifiers which can be used to look up this feature 774 | - `country`: `string`, for features entirely within a country, the id for that country 775 | - `groups`: `[string]`, the ids other features this feature is entirely within 776 | - `members`: `[string]`, the ids of other features this feature entirely contains, the inverse of `groups` 777 | - `level`: `string`, the rough geographic classification of this feature 778 | - `world` 779 | - `unitedNations`: United Nations 780 | - `union`: European Union 781 | - `subunion`: Outermost Regions of the EU, Overseas Countries and Territories of the EU 782 | - `region`: Africa, Americas, Antarctica, Asia, Europe, Oceania 783 | - `subregion`: Sub-Saharan Africa, North America, Micronesia, etc. 784 | - `intermediateRegion`: Eastern Africa, South America, Channel Islands, etc. 785 | - `sharedLandform`: Great Britain, Macaronesia, Mariana Islands, etc. 786 | - `country`: Ethiopia, Brazil, United States, etc. 787 | - `subcountryGroup`: British Overseas Territories, Crown Dependencies, etc. 788 | - `territory`: Puerto Rico, Gurnsey, Hong Kong, etc. 789 | - `subterritory`: Sark, Ascension Island, Diego Garcia, etc. 790 | - `isoStatus`: `string`, the status of this feature's ISO 3166-1 code(s), if any 791 | - `official`: officially-assigned 792 | - `excRes`: exceptionally-reserved 793 | - `usrAssn`: user-assigned 794 | - `driveSide`: `string`, the side of the road on which traffic drives within this feature 795 | - `right` 796 | - `left` 797 | - `roadSpeedUnit`: `string`, the speed unit used on traffic signs in this feature 798 | - `mph`: miles per hour 799 | - `km/h`: kilometers per hour 800 | - `roadHeightUnit`: `string`, the length unit used on vehicle height restriction signs in this feature 801 | - `ft`: feet and inches 802 | - `m`: meters 803 | - `callingCodes`: `[string]`, the international calling codes for this feature, sometimes including area codes 804 | 805 | 806 | # RegionFeatureCollection 807 | 808 | A GeoJSON feature collection containing RegionFeature objects. 809 | -------------------------------------------------------------------------------- /tests/country-coder.spec.ts: -------------------------------------------------------------------------------- 1 | import * as coder from '../src/country-coder'; 2 | 3 | function tuple(...data: T) { 4 | return data; 5 | } 6 | 7 | describe('country-coder', () => { 8 | describe('borders', () => { 9 | it('exports borders as a feature collection', () => { 10 | expect(coder.borders).toHaveProperty('features'); 11 | }); 12 | 13 | describe('properties', () => { 14 | it('all identifying values are unique', () => { 15 | let ids = {}; 16 | let identifierProps = [ 17 | 'iso1A2', 18 | 'iso1A3', 19 | 'm49', 20 | 'wikidata', 21 | 'emojiFlag', 22 | 'ccTLD', 23 | 'nameEn' 24 | ]; 25 | for (let i in coder.borders.features) { 26 | let identifiers = identifierProps 27 | .map(function (prop) { 28 | return coder.borders.features[i].properties[prop]; 29 | }) 30 | .concat(coder.borders.features[i].properties.aliases || []) 31 | .filter(Boolean); 32 | for (let j in identifiers) { 33 | let id = identifiers[j]; 34 | expect(ids[id]).toBeUndefined(); 35 | ids[id] = true; 36 | } 37 | } 38 | }); 39 | 40 | it('each feature has either member features or geometry but not both', () => { 41 | for (let i in coder.borders.features) { 42 | let feature = coder.borders.features[i]; 43 | let hasMembers = feature.properties.members && feature.properties.members.length; 44 | expect(hasMembers || feature.geometry || null).not.toBeNull(); 45 | expect((hasMembers && feature.geometry) || null).toBeNull(); 46 | } 47 | }); 48 | }); 49 | 50 | describe('id', () => { 51 | it('assigns unique id to every feature', () => { 52 | let ids = {}; 53 | for (let i in coder.borders.features) { 54 | let id = coder.borders.features[i].properties.id; 55 | expect(id).not.toBeNull(); 56 | expect(ids[id]).toBeUndefined(); 57 | ids[id] = true; 58 | } 59 | }); 60 | }); 61 | 62 | describe('level', () => { 63 | it('assigns appropriate level values', () => { 64 | expect(coder.feature('AC')?.properties.level).toBe('subterritory'); 65 | expect(coder.feature('SH-HL')?.properties.level).toBe('subterritory'); 66 | expect(coder.feature('DG')?.properties.level).toBe('subterritory'); 67 | expect(coder.feature('CP')?.properties.level).toBe('subterritory'); 68 | expect(coder.feature('Alderney')?.properties.level).toBe('subterritory'); 69 | expect(coder.feature('Akrotiri')?.properties.level).toBe('subterritory'); 70 | expect(coder.feature('FX')?.properties.level).toBe('subterritory'); 71 | expect(coder.feature('IC')?.properties.level).toBe('subterritory'); 72 | 73 | expect(coder.feature('SBA')?.properties.level).toBe('territory'); 74 | expect(coder.feature('EA')?.properties.level).toBe('territory'); 75 | expect(coder.feature('IM')?.properties.level).toBe('territory'); 76 | expect(coder.feature('SH')?.properties.level).toBe('territory'); 77 | expect(coder.feature('IO')?.properties.level).toBe('territory'); 78 | expect(coder.feature('PR')?.properties.level).toBe('territory'); 79 | expect(coder.feature('GU')?.properties.level).toBe('territory'); 80 | expect(coder.feature('GB-SCT')?.properties.level).toBe('territory'); 81 | expect(coder.feature('Bir Tawil')?.properties.level).toBe('territory'); 82 | expect(coder.feature('East Malaysia')?.properties.level).toBe('territory'); 83 | expect(coder.feature('Cook Islands')?.properties.level).toBe('territory'); 84 | expect(coder.feature('Niue')?.properties.level).toBe('territory'); 85 | 86 | expect(coder.feature('US')?.properties.level).toBe('country'); 87 | expect(coder.feature('CA')?.properties.level).toBe('country'); 88 | expect(coder.feature('GB')?.properties.level).toBe('country'); 89 | expect(coder.feature('NZ')?.properties.level).toBe('country'); 90 | expect(coder.feature('IL')?.properties.level).toBe('country'); 91 | expect(coder.feature('PS')?.properties.level).toBe('country'); 92 | expect(coder.feature('XK')?.properties.level).toBe('country'); 93 | 94 | expect(coder.feature('BOTS')?.properties.level).toBe('subcountryGroup'); 95 | 96 | expect(coder.feature('OMR')?.properties.level).toBe('subunion'); 97 | expect(coder.feature('OCT')?.properties.level).toBe('subunion'); 98 | 99 | expect(coder.feature('EU')?.properties.level).toBe('union'); 100 | 101 | expect(coder.feature('UN')?.properties.level).toBe('unitedNations'); 102 | 103 | expect(coder.feature('001')?.properties.level).toBe('world'); 104 | }); 105 | 106 | it('each feature may have only one group per level (except North America)', () => { 107 | for (let i in coder.borders.features) { 108 | let feature = coder.borders.features[i]; 109 | let groups = feature.properties.groups; 110 | if (groups) { 111 | groups = groups.slice().filter(function (group) { 112 | // North America and Northern America are overlapping subregions 113 | // defined by the UN, but ignore that here 114 | return group !== '003'; 115 | }); 116 | let levels = groups.map(function (group) { 117 | return coder.feature(group)?.properties.level; 118 | }); 119 | levels.push(feature.properties.level); 120 | expect(levels.length).toBe([...new Set(levels)].length); 121 | } 122 | } 123 | }); 124 | }); 125 | }); 126 | 127 | describe('feature', () => { 128 | it('does not find feature for empty string', () => { 129 | expect(coder.feature('')).toBeNull(); 130 | }); 131 | 132 | it('does not find feature for garbage string', () => { 133 | expect(coder.feature('fv 239uasˇÁ¨·´€Óı¨ıÎ∆πˆç´œª -aÔ˚øØTˇ°\\asdf \nK')).toBeNull(); 134 | }); 135 | 136 | describe('by ISO 3166-1 alpha-2', () => { 137 | it('finds feature by uppercase code: US', () => { 138 | expect(coder.feature('US')?.properties.iso1N3).toBe('840'); 139 | }); 140 | 141 | it('finds feature by lowercase code: us', () => { 142 | expect(coder.feature('us')?.properties.iso1N3).toBe('840'); 143 | }); 144 | 145 | it('finds feature by mixed-case code: Us', () => { 146 | expect(coder.feature('Us')?.properties.iso1N3).toBe('840'); 147 | }); 148 | 149 | it('does not find feature for unassigned code in range: AB', () => { 150 | expect(coder.feature('AB'))?.toBeNull(); 151 | }); 152 | }); 153 | 154 | describe('by ISO 3166-1 alpha-3', () => { 155 | it('finds features by uppercase codes', () => { 156 | expect(coder.feature('AND')?.properties.iso1A2).toBe('AD'); 157 | expect(coder.feature('BES')?.properties.iso1A2).toBe('BQ'); 158 | expect(coder.feature('ETH')?.properties.iso1A2).toBe('ET'); 159 | expect(coder.feature('SGS')?.properties.iso1A2).toBe('GS'); 160 | expect(coder.feature('SRB')?.properties.iso1A2).toBe('RS'); 161 | expect(coder.feature('USA')?.properties.iso1A2).toBe('US'); 162 | }); 163 | 164 | it('finds features by lowercase codes', () => { 165 | expect(coder.feature('and')?.properties.iso1A2).toBe('AD'); 166 | expect(coder.feature('bes')?.properties.iso1A2).toBe('BQ'); 167 | expect(coder.feature('eth')?.properties.iso1A2).toBe('ET'); 168 | expect(coder.feature('sgs')?.properties.iso1A2).toBe('GS'); 169 | expect(coder.feature('srb')?.properties.iso1A2).toBe('RS'); 170 | expect(coder.feature('usa')?.properties.iso1A2).toBe('US'); 171 | }); 172 | 173 | it('finds features by mixed-case codes', () => { 174 | expect(coder.feature('And')?.properties.iso1A2).toBe('AD'); 175 | expect(coder.feature('Bes')?.properties.iso1A2).toBe('BQ'); 176 | expect(coder.feature('Eth')?.properties.iso1A2).toBe('ET'); 177 | expect(coder.feature('Sgs')?.properties.iso1A2).toBe('GS'); 178 | expect(coder.feature('Srb')?.properties.iso1A2).toBe('RS'); 179 | expect(coder.feature('Usa')?.properties.iso1A2).toBe('US'); 180 | }); 181 | 182 | it('does not find features for unassigned codes in range', () => { 183 | expect(coder.feature('ABC')).toBeNull(); 184 | expect(coder.feature('Abc')).toBeNull(); 185 | expect(coder.feature('abc')).toBeNull(); 186 | }); 187 | }); 188 | 189 | describe('by ISO 3166-1 numeric-3 / M49', () => { 190 | it('finds feature by string: "840"', () => { 191 | expect(coder.feature('840')?.properties.iso1A2).toBe('US'); 192 | }); 193 | 194 | it('finds feature by three-digit number: 840', () => { 195 | expect(coder.feature(840)?.properties.iso1A2).toBe('US'); 196 | }); 197 | 198 | it('finds feature by two-digit number: 61', () => { 199 | expect(coder.feature(61)?.properties.wikidata).toBe('Q35942'); 200 | }); 201 | 202 | it('finds feature by one-digit number: 2', () => { 203 | expect(coder.feature(2)?.properties.wikidata).toBe('Q15'); 204 | }); 205 | 206 | it('finds feature by number with extra precision: 840.000', () => { 207 | expect(coder.feature(840.0)?.properties.iso1A2).toBe('US'); 208 | }); 209 | 210 | it('finds world feature: "001"', () => { 211 | expect(coder.feature('001')?.properties.wikidata).toBe('Q2'); 212 | }); 213 | 214 | it('does not find feature for unassigned code in range: "123"', () => { 215 | expect(coder.feature('123')).toBeNull(); 216 | }); 217 | 218 | it('does not find feature for number outside range: 1234', () => { 219 | expect(coder.feature(1234)).toBeNull(); 220 | }); 221 | }); 222 | 223 | describe('by emoji flag sequence', () => { 224 | it('finds feature for emoji flag sequence: 🇺🇸', () => { 225 | expect(coder.feature('🇺🇸')?.properties.iso1N3).toBe('840'); 226 | }); 227 | 228 | it('does not find feature for unassigned emoji flag sequence: 🇦🇧', () => { 229 | expect(coder.feature('🇦🇧')).toBeNull(); 230 | }); 231 | }); 232 | 233 | describe('by ccTLD', () => { 234 | it('finds feature by uppercase code: .US', () => { 235 | expect(coder.feature('.US')?.properties.iso1N3).toBe('840'); 236 | }); 237 | 238 | it('finds feature by lowercase code: .us', () => { 239 | expect(coder.feature('.us')?.properties.iso1N3).toBe('840'); 240 | }); 241 | 242 | it('finds feature by mixed-case code: .Us', () => { 243 | expect(coder.feature('.Us')?.properties.iso1N3).toBe('840'); 244 | }); 245 | 246 | it('does not find feature for unassigned code in range: .AB', () => { 247 | expect(coder.feature('.AB')).toBeNull(); 248 | }); 249 | 250 | it('finds United Kingdom feature by code: .uk', () => { 251 | expect(coder.feature('.uk')?.properties.iso1N3).toBe('826'); 252 | }); 253 | 254 | it('does not find United Kingdom feature by code: .gb', () => { 255 | expect(coder.feature('.gb')).toBeNull(); 256 | }); 257 | }); 258 | 259 | describe('by Wikidata QID', () => { 260 | it('finds feature by uppercase QID: Q30', () => { 261 | expect(coder.feature('Q30')?.properties.iso1A2).toBe('US'); 262 | }); 263 | 264 | it('finds feature by lowercase QID: q30', () => { 265 | expect(coder.feature('q30')?.properties.iso1A2).toBe('US'); 266 | }); 267 | 268 | it('finds feature with no ISO or M49 codes by QID: Q153732', () => { 269 | expect(coder.feature('Q153732')?.properties.nameEn).toBe('Mariana Islands'); 270 | }); 271 | 272 | it('does not find feature for non-feature QID: Q123456', () => { 273 | expect(coder.feature('Q123456')).toBeNull(); 274 | }); 275 | }); 276 | 277 | describe('by English name', () => { 278 | it('finds feature for exact name: Bhutan', () => { 279 | expect(coder.feature('Bhutan')?.properties.iso1A2).toBe('BT'); 280 | }); 281 | it('finds feature for exact name containing "And": Andorra', () => { 282 | expect(coder.feature('Andorra')?.properties.iso1A2).toBe('AD'); 283 | }); 284 | it('finds feature for lowercase name containing "and": andorra', () => { 285 | expect(coder.feature('andorra')?.properties.iso1A2).toBe('AD'); 286 | }); 287 | it('finds feature for name containing "the": Northern Europe', () => { 288 | expect(coder.feature('Northern Europe')?.properties.m49).toBe('154'); 289 | }); 290 | it('finds feature for name with extra "The": The United States of America', () => { 291 | expect(coder.feature('The United States of America')?.properties.iso1A2).toBe('US'); 292 | }); 293 | it('finds feature for name without "The": Gambia', () => { 294 | expect(coder.feature('Gambia')?.properties.iso1A2).toBe('GM'); 295 | }); 296 | it('finds feature not in country for name: Bir Tawil', () => { 297 | expect(coder.feature('Bir Tawil')?.properties.wikidata).toBe('Q620634'); 298 | }); 299 | }); 300 | 301 | describe('by alias', () => { 302 | it('finds by European Commission codes', () => { 303 | expect(coder.feature('EL')?.properties.iso1N3).toBe('300'); 304 | expect(coder.feature('el')?.properties.iso1N3).toBe('300'); 305 | expect(coder.feature('UK')?.properties.iso1N3).toBe('826'); 306 | }); 307 | 308 | it('finds by transitionally-reserved codes', () => { 309 | expect(coder.feature('BU')?.properties.iso1N3).toBe('104'); 310 | }); 311 | 312 | it('finds by indeterminately-reserved codes', () => { 313 | expect(coder.feature('PI')?.properties.iso1N3).toBe('608'); 314 | expect(coder.feature('RP')?.properties.iso1N3).toBe('608'); 315 | }); 316 | 317 | it('finds by ISO 3166-2 codes', () => { 318 | expect(coder.feature('UM-71')?.properties.nameEn).toBe('Midway Atoll'); 319 | expect(coder.feature('UM71')?.properties.nameEn).toBe('Midway Atoll'); 320 | expect(coder.feature('UM 71')?.properties.nameEn).toBe('Midway Atoll'); 321 | expect(coder.feature('US-AK')?.properties.nameEn).toBe('Alaska'); 322 | }); 323 | 324 | it('finds by deleted codes', () => { 325 | expect(coder.feature('MI')?.properties.nameEn).toBe('Midway Atoll'); 326 | expect(coder.feature('MID')?.properties.nameEn).toBe('Midway Atoll'); 327 | expect(coder.feature('488')?.properties.nameEn).toBe('Midway Atoll'); 328 | }); 329 | 330 | it('finds by common abbreviations', () => { 331 | expect(coder.feature('CONUS')?.properties.nameEn).toBe('Contiguous United States'); 332 | expect(coder.feature('SBA')?.properties.wikidata).toBe('Q37362'); 333 | expect(coder.feature('BOTS')?.properties.wikidata).toBe('Q46395'); 334 | expect(coder.feature('UKOTS')?.properties.wikidata).toBe('Q46395'); 335 | }); 336 | }); 337 | 338 | describe('by location', () => { 339 | it('returns country feature by default', () => { 340 | expect(coder.feature([12.59, 55.68])?.properties.iso1A2).toBe('DK'); 341 | expect(coder.feature([-74, 40.6])?.properties.iso1A2).toBe('US'); 342 | expect(coder.feature([-12.3, -37.1])?.properties.iso1A2).toBe('GB'); 343 | expect(coder.feature([153, -27.4])?.properties.iso1A2).toBe('AU'); 344 | }); 345 | it('returns country feature for country level', () => { 346 | expect(coder.feature([12.59, 55.68], { level: 'country' })?.properties.iso1A2).toBe('DK'); 347 | expect(coder.feature([-74, 40.6], { level: 'country' })?.properties.iso1A2).toBe('US'); 348 | expect(coder.feature([-12.3, -37.1], { level: 'country' })?.properties.iso1A2).toBe('GB'); 349 | expect(coder.feature([153, -27.4], { level: 'country' })?.properties.iso1A2).toBe('AU'); 350 | }); 351 | it('returns next-higher-level feature for country level where no country exists (Bir Tawil)', () => { 352 | expect(coder.feature([33.75, 21.87])?.properties.m49).toBe('015'); 353 | expect(coder.feature([33.75, 21.87], { level: 'country' })?.properties.m49).toBe('015'); 354 | }); 355 | it('returns null for country level where no country exists', () => { 356 | expect(coder.feature([33.75, 21.87], { maxLevel: 'country' })).toBeNull(); 357 | expect(coder.feature([33.75, 21.87], { level: 'country', maxLevel: 'country' })).toBeNull(); 358 | }); 359 | 360 | it('returns subterritory feature for subterritory level', () => { 361 | expect(coder.feature([-12.3, -37.1], { level: 'subterritory' })?.properties.iso1A2).toBe( 362 | 'TA' 363 | ); 364 | }); 365 | it('returns country feature for subterritory level where no subterritory or territory exists', () => { 366 | expect(coder.feature([-79.4, 43.7], { level: 'subterritory' })?.properties.iso1A2).toBe( 367 | 'CA' 368 | ); 369 | }); 370 | 371 | it('returns territory feature for territory level', () => { 372 | expect(coder.feature([-12.3, -37.1], { level: 'territory' })?.properties.iso1A2).toBe('SH'); 373 | expect(coder.feature([33.75, 21.87], { level: 'territory' })?.properties.wikidata).toBe( 374 | 'Q620634' 375 | ); 376 | expect(coder.feature([33.75, 21.87], { level: 'territory' })?.properties.wikidata).toBe( 377 | 'Q620634' 378 | ); 379 | }); 380 | it('returns country feature for territory level where no territory exists', () => { 381 | expect(coder.feature([-79.4, 43.7], { level: 'territory' })?.properties.iso1A2).toBe('CA'); 382 | }); 383 | it('returns null for territory level where no territory exists', () => { 384 | expect( 385 | coder.feature([-79.4, 43.7], { level: 'territory', maxLevel: 'territory' }) 386 | ).toBeNull(); 387 | }); 388 | 389 | it('returns intermediateRegion feature for intermediateRegion level', () => { 390 | expect(coder.feature([-12.3, -37.1], { level: 'intermediateRegion' })?.properties.m49).toBe( 391 | '011' 392 | ); 393 | }); 394 | it('returns subregion feature for subregion level', () => { 395 | expect(coder.feature([-12.3, -37.1], { level: 'subregion' })?.properties.m49).toBe('202'); 396 | }); 397 | it('returns region feature for region level', () => { 398 | expect(coder.feature([-12.3, -37.1], { level: 'region' })?.properties.m49).toBe('002'); 399 | }); 400 | it('returns union feature for union level', () => { 401 | expect(coder.feature([2.35, 48.85], { level: 'union' })?.properties.iso1A2).toBe('EU'); 402 | }); 403 | 404 | it('returns null for invalid level options', () => { 405 | expect(coder.feature([-12.3, -37.1], { level: 'planet' })).toBeNull(); 406 | expect(coder.feature([-12.3, -37.1], { maxLevel: 'mars' })).toBeNull(); 407 | expect(coder.feature([-12.3, -37.1], { maxLevel: 'subterritory' })).toBeNull(); 408 | expect( 409 | coder.feature([-12.3, -37.1], { level: 'country', maxLevel: 'subterritory' }) 410 | ).toBeNull(); 411 | }); 412 | it('returns Antarctica for South Pole, country level', () => { 413 | expect(coder.feature([0, -90])?.properties.iso1A2).toBe('AQ'); 414 | expect(coder.feature([0, -90], { level: 'country' })?.properties.iso1A2).toBe('AQ'); 415 | }); 416 | it('returns null for North Pole', () => { 417 | expect(coder.feature([0, 90])).toBeNull(); 418 | expect(coder.feature([0, 90], { level: 'country' })).toBeNull(); 419 | expect(coder.feature([0, 90], { level: 'subterritory' })).toBeNull(); 420 | }); 421 | }); 422 | }); 423 | 424 | describe('iso1A2Code', () => { 425 | describe('by ISO 3166-1 alpha-2', () => { 426 | it('finds by ISO 3166-1 alpha-2 code: US', () => { 427 | expect(coder.iso1A2Code('US')).toBe('US'); 428 | }); 429 | 430 | it('does not find for unassigned alpha-2 code: AB', () => { 431 | expect(coder.iso1A2Code('AB')).toBeNull(); 432 | }); 433 | }); 434 | 435 | describe('by ISO 3166-1 alpha-3', () => { 436 | it('finds by ISO 3166-1 alpha-3 code: USA', () => { 437 | expect(coder.iso1A2Code('USA')).toBe('US'); 438 | }); 439 | 440 | it('does not find feature for unassigned alpha-3 code: ABC', () => { 441 | expect(coder.iso1A2Code('ABC')).toBeNull(); 442 | }); 443 | }); 444 | 445 | describe('by ISO 3166-1 numeric-3', () => { 446 | it('finds by ISO 3166-1 numeric-3 code: "840"', () => { 447 | expect(coder.iso1A2Code('840')).toBe('US'); 448 | }); 449 | 450 | it('does not find for unassigned numeric-3 code: "123"', () => { 451 | expect(coder.iso1A2Code('123')).toBeNull(); 452 | }); 453 | }); 454 | 455 | describe('finds by emoji flag sequence', () => { 456 | it('finds feature for emoji flag sequence: 🇺🇸', () => { 457 | expect(coder.iso1A2Code('🇺🇸')).toBe('US'); 458 | }); 459 | 460 | it('does not find for unassigned emoji flag sequence: 🇦🇧', () => { 461 | expect(coder.iso1A2Code('🇦🇧')).toBeNull(); 462 | }); 463 | }); 464 | 465 | describe('by ccTLD', () => { 466 | it('finds by ccTLD code: .us', () => { 467 | expect(coder.iso1A2Code('.us')).toBe('US'); 468 | }); 469 | 470 | it('does not find for unassigned ccTLD code: .ab', () => { 471 | expect(coder.iso1A2Code('.ab')).toBeNull(); 472 | }); 473 | 474 | it('finds United Kingdom by ccTLD code: .uk', () => { 475 | expect(coder.iso1A2Code('.uk')).toBe('GB'); 476 | }); 477 | 478 | it('does not find United Kingdom by ccTLD code: .gb', () => { 479 | expect(coder.iso1A2Code('.gb')).toBeNull(); 480 | }); 481 | 482 | it('finds Germany by ccTLD code: .de', () => { 483 | expect(coder.iso1A2Code('.de')).toBe('DE'); 484 | }); 485 | }); 486 | 487 | describe('by Wikidata QID', () => { 488 | it('finds by Wikidata QID: Q30', () => { 489 | expect(coder.iso1A2Code('Q30')).toBe('US'); 490 | }); 491 | 492 | it('does not find for non-feature Wikidata QID code: Q123456', () => { 493 | expect(coder.iso1A2Code('Q123456')).toBeNull(); 494 | }); 495 | }); 496 | 497 | describe('by alias', () => { 498 | it('finds Greece by European Commission code: EL', () => { 499 | expect(coder.iso1A2Code('EL')).toBe('GR'); 500 | }); 501 | 502 | it('finds United Kingdom by European Commission code: UK', () => { 503 | expect(coder.iso1A2Code('UK')).toBe('GB'); 504 | }); 505 | 506 | it('finds Myanmar by transitionally-reserved code: BU', () => { 507 | expect(coder.iso1A2Code('BU')).toBe('MM'); 508 | }); 509 | 510 | it('finds Philippines by indeterminately-reserved code 1: PI', () => { 511 | expect(coder.iso1A2Code('PI')).toBe('PH'); 512 | }); 513 | 514 | it('finds Philippines by indeterminately-reserved code 2: RP', () => { 515 | expect(coder.iso1A2Code('RP')).toBe('PH'); 516 | }); 517 | }); 518 | describe('by M49', () => { 519 | it('does not find for feature with geography but no ISO code', () => { 520 | expect(coder.iso1A2Code('680')).toBeNull(); 521 | }); 522 | it('does not find for feature with no geography and no ISO code', () => { 523 | expect(coder.iso1A2Code('142')).toBeNull(); 524 | }); 525 | }); 526 | describe('by location, country level', () => { 527 | it('codes location in officially-assigned country: Toronto, Canada as CA', () => { 528 | expect(coder.iso1A2Code([-79.4, 43.7], { level: 'country' })).toBe('CA'); 529 | }); 530 | 531 | it('codes location in non-ISO territory of officially-assigned country: New York, United States as US', () => { 532 | expect(coder.iso1A2Code([-74, 40.6], { level: 'country' })).toBe('US'); 533 | }); 534 | 535 | it('codes location in officially-assigned country, outside but surrounded by EU: Geneva, Switzerland as CH', () => { 536 | expect(coder.iso1A2Code([6.1, 46.2], { level: 'country' })).toBe('CH'); 537 | }); 538 | 539 | it('codes location in officially-assigned country, in EU, outside Eurozone: Copenhagen, Denmark as DK', () => { 540 | expect(coder.iso1A2Code([12.59, 55.68], { level: 'country' })).toBe('DK'); 541 | }); 542 | 543 | it('codes location in officially-assigned country, in EU, in Eurozone: Berlin, Germany as DE', () => { 544 | expect(coder.iso1A2Code([13.4, 52.5], { level: 'country' })).toBe('DE'); 545 | }); 546 | 547 | it('codes location in officially-assigned subfeature of officially-assigned country: Isle of Man, United Kingdom as GB', () => { 548 | expect(coder.iso1A2Code([-4.5, 54.2], { level: 'country' })).toBe('GB'); 549 | }); 550 | 551 | it('codes location in exceptionally-reserved subfeature of officially-assigned country: Paris, Metropolitan France as FR', () => { 552 | expect(coder.iso1A2Code([2.35, 48.85], { level: 'country' })).toBe('FR'); 553 | }); 554 | 555 | it('codes location in exceptionally-reserved subfeature of officially-assigned subfeature of officially-assigned country: Tristan da Cunha, SH, UK as GB', () => { 556 | expect(coder.iso1A2Code([-12.3, -37.1], { level: 'country' })).toBe('GB'); 557 | }); 558 | 559 | it('codes location in user-assigned, de facto country: Kosovo as XK', () => { 560 | expect(coder.iso1A2Code([21, 42.6], { level: 'country' })).toBe('XK'); 561 | }); 562 | 563 | it('codes location in exclave of officially-assigned country: Sokh District, Uzbekistan as UZ', () => { 564 | expect(coder.iso1A2Code([71.13, 39.96], { level: 'country' })).toBe('UZ'); 565 | }); 566 | 567 | it('codes location in feature without an ISO code: Sark as GB', () => { 568 | expect(coder.iso1A2Code([-2.35, 49.43], { level: 'country' })).toBe('GB'); 569 | }); 570 | 571 | it('codes South Pole as AQ', () => { 572 | expect(coder.iso1A2Code([0, -90], { level: 'country' })).toBe('AQ'); 573 | }); 574 | 575 | it('does not code North Pole', () => { 576 | expect(coder.iso1A2Code([0, 90], { level: 'country' })).toBeNull(); 577 | }); 578 | 579 | it('does not code location in Bir Tawil', () => { 580 | expect(coder.iso1A2Code([33.75, 21.87])).toBeNull(); 581 | expect(coder.iso1A2Code([33.75, 21.87], { level: 'country' })).toBeNull(); 582 | }); 583 | }); 584 | describe('by location, territory level', () => { 585 | it('codes location in officially-assigned country: Toronto, Canada as CA', () => { 586 | expect(coder.iso1A2Code([-79.4, 43.7], { level: 'territory' })).toBe('CA'); 587 | }); 588 | 589 | it('codes location in non-ISO territory of officially-assigned country: New York, United States as US', () => { 590 | expect(coder.iso1A2Code([-74, 40.6], { level: 'territory' })).toBe('US'); 591 | }); 592 | 593 | it('codes location in officially-assigned country, outside but surrounded by EU: Geneva, Switzerland as CH', () => { 594 | expect(coder.iso1A2Code([6.1, 46.2], { level: 'territory' })).toBe('CH'); 595 | }); 596 | 597 | it('codes location in officially-assigned country, in EU, outside Eurozone: Copenhagen, Denmark as DK', () => { 598 | expect(coder.iso1A2Code([12.59, 55.68], { level: 'territory' })).toBe('DK'); 599 | }); 600 | 601 | it('codes location in officially-assigned country, in EU, in Eurozone: Berlin, Germany as DE', () => { 602 | expect(coder.iso1A2Code([13.4, 52.5], { level: 'territory' })).toBe('DE'); 603 | }); 604 | 605 | it('codes location in officially-assigned subfeature of officially-assigned country: Isle of Man, United Kingdom as IM', () => { 606 | expect(coder.iso1A2Code([-4.5, 54.2], { level: 'territory' })).toBe('IM'); 607 | }); 608 | 609 | it('codes location in exceptionally-reserved subfeature of officially-assigned country: Paris, Metropolitan France as FR', () => { 610 | expect(coder.iso1A2Code([2.35, 48.85], { level: 'territory' })).toBe('FR'); 611 | }); 612 | 613 | it('codes location in exceptionally-reserved subfeature of officially-assigned subfeature of officially-assigned country: Tristan da Cunha, SH, UK as SH', () => { 614 | expect(coder.iso1A2Code([-12.3, -37.1], { level: 'territory' })).toBe('SH'); 615 | }); 616 | 617 | it('codes location in user-assigned, de facto country: Kosovo as XK', () => { 618 | expect(coder.iso1A2Code([21, 42.6], { level: 'territory' })).toBe('XK'); 619 | }); 620 | 621 | it('codes location in exclave of officially-assigned country: Sokh District, Uzbekistan as UZ', () => { 622 | expect(coder.iso1A2Code([71.13, 39.96], { level: 'territory' })).toBe('UZ'); 623 | }); 624 | 625 | it('codes location in feature without an ISO code: Sark as GG', () => { 626 | expect(coder.iso1A2Code([-2.35, 49.43], { level: 'territory' })).toBe('GG'); 627 | }); 628 | 629 | it('codes South Pole as AQ', () => { 630 | expect(coder.iso1A2Code([0, -90], { level: 'territory' })).toBe('AQ'); 631 | }); 632 | 633 | it('does not code North Pole', () => { 634 | expect(coder.iso1A2Code([0, 90], { level: 'territory' })).toBeNull(); 635 | }); 636 | 637 | it('does not code location in Bir Tawil', () => { 638 | expect(coder.iso1A2Code([33.75, 21.87], { level: 'territory' })).toBeNull(); 639 | }); 640 | }); 641 | describe('by GeoJSON point feature, country level', () => { 642 | it('codes location in officially-assigned country: New York, United States as US', () => { 643 | const coords = tuple(-74, 40.6); 644 | 645 | let pointFeature = { 646 | type: 'Feature', 647 | properties: null, 648 | geometry: { 649 | type: 'Point', 650 | coordinates: coords 651 | } 652 | }; 653 | expect(coder.iso1A2Code(pointFeature)).toBe('US'); 654 | }); 655 | }); 656 | describe('by GeoJSON point geometry, country level', () => { 657 | it('codes location in officially-assigned country: New York, United States as US', () => { 658 | let pointGeometry = { 659 | type: 'Point', 660 | coordinates: tuple(-74, 40.6) 661 | }; 662 | expect(coder.iso1A2Code(pointGeometry)).toBe('US'); 663 | }); 664 | }); 665 | }); 666 | 667 | // this doesn't need extensive tests since it's just a fetcher using `feature` 668 | describe('iso1A3Code', () => { 669 | it('codes location in officially-assigned country: New York, United States as USA', () => { 670 | expect(coder.iso1A3Code([-74, 40.6])).toBe('USA'); 671 | expect(coder.iso1A3Code([-74, 40.6], { level: 'country' })).toBe('USA'); 672 | }); 673 | 674 | it('codes location in user-assigned, de facto country: Kosovo as XKX', () => { 675 | expect(coder.iso1A3Code([21, 42.6])).toBe('XKX'); 676 | expect(coder.iso1A3Code([21, 42.6], { level: 'country' })).toBe('XKX'); 677 | }); 678 | 679 | it('does not code location of North Pole', () => { 680 | expect(coder.iso1A3Code([0, 90])).toBeNull(); 681 | expect(coder.iso1A3Code([0, 90], { level: 'country' })).toBeNull(); 682 | }); 683 | 684 | it('does not code location in Bir Tawil', () => { 685 | expect(coder.iso1A3Code([33.75, 21.87])).toBeNull(); 686 | expect(coder.iso1A3Code([33.75, 21.87], { level: 'country' })).toBeNull(); 687 | }); 688 | 689 | it('does not code feature without alpha-3 code by identifier', () => { 690 | expect(coder.iso1A3Code('Bir Tawil')).toBeNull(); 691 | expect(coder.iso1A3Code('Sark')).toBeNull(); 692 | expect(coder.iso1A3Code('830')).toBeNull(); 693 | expect(coder.iso1A3Code('Northern America')).toBeNull(); 694 | expect(coder.iso1A3Code('Oceania')).toBeNull(); 695 | }); 696 | }); 697 | 698 | // this doesn't need extensive tests since it's just a fetcher using `feature` 699 | describe('iso1N3Code', () => { 700 | it('codes location in officially-assigned country: New York, United States as 840', () => { 701 | expect(coder.iso1N3Code([-74, 40.6], { level: 'country' })).toBe('840'); 702 | }); 703 | 704 | it('does not have code for location in user-assigned, de facto country: Kosovo', () => { 705 | expect(coder.iso1N3Code([21, 42.6], { level: 'country' })).toBeNull(); 706 | }); 707 | 708 | it('does not code non-geography, non-ISO feature by Wikidata QID: Q48', () => { 709 | expect(coder.iso1N3Code('Q48')).toBeNull(); 710 | }); 711 | 712 | it('does not code North Pole', () => { 713 | expect(coder.iso1N3Code([0, 90], { level: 'country' })).toBeNull(); 714 | }); 715 | 716 | it('does not code location in Bir Tawil', () => { 717 | expect(coder.iso1N3Code([33.75, 21.87], { level: 'country' })).toBeNull(); 718 | }); 719 | }); 720 | 721 | describe('m49Code', () => { 722 | it('codes location in officially-assigned country: New York, United States as 840', () => { 723 | expect(coder.m49Code([-74, 40.6], { level: 'country' })).toBe('840'); 724 | }); 725 | 726 | it('codes non-geography, non-ISO feature by Wikidata QID: Q48 as 142', () => { 727 | expect(coder.m49Code('Q48')).toBe('142'); 728 | }); 729 | 730 | it('does not have code for location in user-assigned, de facto country: Kosovo', () => { 731 | expect(coder.m49Code([21, 42.6], { maxLevel: 'country' })).toBeNull(); 732 | expect(coder.m49Code([21, 42.6], { level: 'country', maxLevel: 'country' })).toBeNull(); 733 | }); 734 | 735 | it('does not code location of North Pole', () => { 736 | expect(coder.m49Code([0, 90], { level: 'country' })).toBeNull(); 737 | }); 738 | 739 | it('does not code "Bir Tawil"', () => { 740 | expect(coder.m49Code('Bir Tawil')).toBeNull(); 741 | }); 742 | it('codes location in Bir Tawil as 015', () => { 743 | expect(coder.m49Code([33.75, 21.87])).toBe('015'); 744 | expect(coder.m49Code([33.75, 21.87], { level: 'country' })).toBe('015'); 745 | }); 746 | 747 | it('does not code location in Bir Tawil, maxLevel=country', () => { 748 | expect(coder.m49Code([33.75, 21.87], { maxLevel: 'country' })).toBeNull(); 749 | expect(coder.m49Code([33.75, 21.87], { level: 'country', maxLevel: 'country' })).toBeNull(); 750 | }); 751 | }); 752 | 753 | // this doesn't need extensive tests since it's just a fetcher using `feature` 754 | describe('wikidataQID', () => { 755 | it('codes location in officially-assigned country: New York, United States as Q30', () => { 756 | expect(coder.wikidataQID([-74, 40.6], { level: 'country' })).toBe('Q30'); 757 | }); 758 | 759 | it('codes location in user-assigned, de facto country: Kosovo as Q1246', () => { 760 | expect(coder.wikidataQID([21, 42.6], { level: 'country' })).toBe('Q1246'); 761 | }); 762 | 763 | it('does not code North Pole', () => { 764 | expect(coder.wikidataQID([0, 90], { level: 'country' })).toBeNull(); 765 | }); 766 | 767 | it('codes Bir Tawil', () => { 768 | expect(coder.wikidataQID('Bir Tawil')).toBe('Q620634'); 769 | }); 770 | }); 771 | 772 | // this doesn't need extensive tests since it's just a fetcher using `feature` 773 | describe('emojiFlag', () => { 774 | it('codes location in officially-assigned country: New York, United States as 🇺🇸', () => { 775 | expect(coder.emojiFlag([-74, 40.6], { level: 'country' })).toBe('🇺🇸'); 776 | }); 777 | 778 | it('codes location in user-assigned, de facto country: Kosovo as 🇽🇰', () => { 779 | expect(coder.emojiFlag([21, 42.6], { level: 'country' })).toBe('🇽🇰'); 780 | }); 781 | 782 | it('does not find for M49 code with no corresponding ISO code', () => { 783 | expect(coder.emojiFlag('150')).toBeNull(); 784 | }); 785 | 786 | it('does not code North Pole', () => { 787 | expect(coder.emojiFlag([0, 90], { level: 'country' })).toBeNull(); 788 | }); 789 | }); 790 | 791 | // this doesn't need extensive tests since it's just a fetcher using `feature` 792 | describe('ccTLD', () => { 793 | it('returns ccTLD in officially-assigned country: New York, United States as .us', () => { 794 | expect(coder.ccTLD([-74, 40.6], { level: 'country' })).toBe('.us'); 795 | }); 796 | 797 | it('returns ccTLD in officially-assigned country: London, United Kingdom as .uk', () => { 798 | expect(coder.ccTLD([0, 51.5], { level: 'country' })).toBe('.uk'); // not .gb 799 | }); 800 | 801 | it('does not return a ccTLD for a region with no ccTLD', () => { 802 | expect(coder.ccTLD('Bir Tawil')).toBeNull(); 803 | }); 804 | 805 | it('does not return a ccTLD for uncovered location North Pole', () => { 806 | expect(coder.ccTLD([0, 90])).toBeNull(); 807 | }); 808 | }); 809 | 810 | describe('iso1A2Codes', () => { 811 | it('codes locations', () => { 812 | expect(coder.iso1A2Codes([-4.5, 54.2])).toStrictEqual(['IM', 'GB', 'UN']); 813 | expect(coder.iso1A2Codes([-2.35, 49.43])).toStrictEqual(['GG', 'GB', 'UN']); 814 | expect(coder.iso1A2Codes([-12.3, -37.1])).toStrictEqual(['TA', 'SH', 'GB', 'UN']); 815 | expect(coder.iso1A2Codes([12.59, 55.68])).toStrictEqual(['DK', 'EU', 'UN']); 816 | expect(coder.iso1A2Codes([2.35, 48.85])).toStrictEqual(['FX', 'FR', 'EU', 'UN']); 817 | expect(coder.iso1A2Codes([-74, 40.6])).toStrictEqual(['US', 'UN']); 818 | expect(coder.iso1A2Codes([21, 42.6])).toStrictEqual(['XK']); 819 | expect(coder.iso1A2Codes([0, -90])).toStrictEqual(['AQ']); 820 | }); 821 | 822 | it('codes bounding boxes', () => { 823 | expect(coder.iso1A2Codes([-4.5, 54.2, -4.4, 54.3])).toStrictEqual(['IM', 'GB', 'UN']); 824 | // area of US overlapping Canada's bounding box but not its polygon 825 | expect(coder.iso1A2Codes([-74, 40.6, -71.3, 44.7])).toStrictEqual(['US', 'UN']); 826 | // area overlapping both US and Canada 827 | expect(coder.iso1A2Codes([-74, 40.6, -71.3, 45])).toStrictEqual(['CA', 'UN', 'US']); 828 | }); 829 | 830 | it('does not code invalid arguments', () => { 831 | expect(coder.iso1A2Codes([] as any)).toStrictEqual([]); 832 | expect(coder.iso1A2Codes([-900, 900])).toStrictEqual([]); 833 | }); 834 | 835 | it('does not code North Pole', () => { 836 | expect(coder.iso1A2Codes([0, 90])).toStrictEqual([]); 837 | expect(coder.iso1A2Codes([-0.1, 89.9, 0, 90])).toStrictEqual([]); 838 | }); 839 | 840 | it('does not code location in Bir Tawil', () => { 841 | expect(coder.iso1A2Codes([33.75, 21.87])).toStrictEqual([]); 842 | }); 843 | }); 844 | 845 | describe('iso1A3Codes', () => { 846 | it('codes locations', () => { 847 | expect(coder.iso1A3Codes([-4.5, 54.2])).toStrictEqual(['IMN', 'GBR']); 848 | expect(coder.iso1A3Codes([-2.35, 49.43])).toStrictEqual(['GGY', 'GBR']); 849 | expect(coder.iso1A3Codes([-12.3, -37.1])).toStrictEqual(['TAA', 'SHN', 'GBR']); 850 | expect(coder.iso1A3Codes([12.59, 55.68])).toStrictEqual(['DNK', 'EUE']); 851 | expect(coder.iso1A3Codes([2.35, 48.85])).toStrictEqual(['FXX', 'FRA', 'EUE']); 852 | expect(coder.iso1A3Codes([-74, 40.6])).toStrictEqual(['USA']); 853 | expect(coder.iso1A3Codes([21, 42.6])).toStrictEqual(['XKX']); 854 | expect(coder.iso1A3Codes([0, -90])).toStrictEqual(['ATA']); 855 | }); 856 | 857 | it('does not code North Pole', () => { 858 | expect(coder.iso1A3Codes([0, 90])).toStrictEqual([]); 859 | }); 860 | 861 | it('does not code location in Bir Tawil', () => { 862 | expect(coder.iso1A3Codes([33.75, 21.87])).toStrictEqual([]); 863 | }); 864 | }); 865 | 866 | describe('iso1N3Codes', () => { 867 | it('codes locations', () => { 868 | expect(coder.iso1N3Codes([-4.5, 54.2])).toStrictEqual(['833', '826']); 869 | expect(coder.iso1N3Codes([-2.35, 49.43])).toStrictEqual(['831', '826']); 870 | expect(coder.iso1N3Codes([-12.3, -37.1])).toStrictEqual(['654', '826']); 871 | expect(coder.iso1N3Codes([12.59, 55.68])).toStrictEqual(['208']); 872 | expect(coder.iso1N3Codes([2.35, 48.85])).toStrictEqual(['249', '250']); 873 | expect(coder.iso1N3Codes([-74, 40.6])).toStrictEqual(['840']); 874 | expect(coder.iso1N3Codes([21, 42.6])).toStrictEqual([]); 875 | expect(coder.iso1N3Codes([0, -90])).toStrictEqual(['010']); 876 | }); 877 | 878 | it('does not code North Pole', () => { 879 | expect(coder.iso1N3Codes([0, 90])).toStrictEqual([]); 880 | }); 881 | 882 | it('does not code location in Bir Tawil', () => { 883 | expect(coder.iso1N3Codes([33.75, 21.87])).toStrictEqual([]); 884 | }); 885 | }); 886 | 887 | describe('m49Codes', () => { 888 | it('codes locations', () => { 889 | // isle of man 890 | expect(coder.m49Codes([-4.5, 54.2])).toStrictEqual(['833', '826', '154', '150', '001']); 891 | expect(coder.m49Codes([-2.35, 49.43])).toStrictEqual([ 892 | '680', 893 | '831', 894 | '826', 895 | '830', 896 | '154', 897 | '150', 898 | '001' 899 | ]); 900 | expect(coder.m49Codes([-12.3, -37.1])).toStrictEqual([ 901 | '654', 902 | '826', 903 | '011', 904 | '202', 905 | '002', 906 | '001' 907 | ]); 908 | expect(coder.m49Codes([12.59, 55.68])).toStrictEqual(['208', '154', '150', '001']); 909 | expect(coder.m49Codes([2.35, 48.85])).toStrictEqual(['249', '250', '155', '150', '001']); 910 | expect(coder.m49Codes([-74, 40.6])).toStrictEqual(['840', '021', '003', '019', '001']); 911 | expect(coder.m49Codes([21, 42.6])).toStrictEqual(['039', '150', '001']); 912 | expect(coder.m49Codes([0, -90])).toStrictEqual(['010', '001']); 913 | expect(coder.m49Codes([33.75, 21.87])).toStrictEqual(['015', '002', '001']); 914 | }); 915 | 916 | it('does not code North Pole', () => { 917 | expect(coder.m49Codes([0, 90])).toStrictEqual([]); 918 | }); 919 | }); 920 | 921 | describe('wikidataQIDs', () => { 922 | it('codes locations', () => { 923 | // isle of man 924 | expect(coder.wikidataQIDs([-4.5, 54.2])).toStrictEqual([ 925 | 'Q9676', 926 | 'Q185086', 927 | 'Q145', 928 | 'Q27479', 929 | 'Q46', 930 | 'Q1065', 931 | 'Q2' 932 | ]); 933 | expect(coder.wikidataQIDs([-2.35, 49.43])).toStrictEqual([ 934 | 'Q3405693', 935 | 'Q25230', 936 | 'Q185086', 937 | 'Q145', 938 | 'Q42314', 939 | 'Q27479', 940 | 'Q46', 941 | 'Q1065', 942 | 'Q2' 943 | ]); 944 | expect(coder.wikidataQIDs([-12.3, -37.1])).toStrictEqual([ 945 | 'Q220982', 946 | 'Q192184', 947 | 'Q46395', 948 | 'Q145', 949 | 'Q4412', 950 | 'Q132959', 951 | 'Q15', 952 | 'Q1065', 953 | 'Q2' 954 | ]); 955 | expect(coder.wikidataQIDs([12.59, 55.68])).toStrictEqual([ 956 | 'Q35', 957 | 'Q756617', 958 | 'Q27479', 959 | 'Q46', 960 | 'Q458', 961 | 'Q1065', 962 | 'Q2' 963 | ]); 964 | expect(coder.wikidataQIDs([2.35, 48.85])).toStrictEqual([ 965 | 'Q212429', 966 | 'Q142', 967 | 'Q27496', 968 | 'Q46', 969 | 'Q458', 970 | 'Q1065', 971 | 'Q2' 972 | ]); 973 | expect(coder.wikidataQIDs([-74, 40.6])).toStrictEqual([ 974 | 'Q578170', 975 | 'Q35657', 976 | 'Q30', 977 | 'Q2017699', 978 | 'Q49', 979 | 'Q828', 980 | 'Q1065', 981 | 'Q2' 982 | ]); 983 | expect(coder.wikidataQIDs([21, 42.6])).toStrictEqual(['Q1246', 'Q27449', 'Q46', 'Q2']); 984 | expect(coder.wikidataQIDs([0, -90])).toStrictEqual(['Q51', 'Q2']); 985 | expect(coder.wikidataQIDs([33.75, 21.87])).toStrictEqual(['Q620634', 'Q27381', 'Q15', 'Q2']); 986 | }); 987 | 988 | it('does not code North Pole', () => { 989 | expect(coder.wikidataQIDs([0, 90])).toStrictEqual([]); 990 | }); 991 | }); 992 | 993 | describe('emojiFlags', () => { 994 | it('codes locations', () => { 995 | // isle of man 996 | expect(coder.emojiFlags([-4.5, 54.2])).toStrictEqual(['🇮🇲', '🇬🇧', '🇺🇳']); 997 | expect(coder.emojiFlags([-2.35, 49.43])).toStrictEqual(['🇬🇬', '🇬🇧', '🇺🇳']); 998 | expect(coder.emojiFlags([-12.3, -37.1])).toStrictEqual(['🇹🇦', '🇸🇭', '🇬🇧', '🇺🇳']); 999 | expect(coder.emojiFlags([12.59, 55.68])).toStrictEqual(['🇩🇰', '🇪🇺', '🇺🇳']); 1000 | expect(coder.emojiFlags([2.35, 48.85])).toStrictEqual(['🇫🇽', '🇫🇷', '🇪🇺', '🇺🇳']); 1001 | expect(coder.emojiFlags([-74, 40.6])).toStrictEqual(['🇺🇸', '🇺🇳']); 1002 | expect(coder.emojiFlags([21, 42.6])).toStrictEqual(['🇽🇰']); 1003 | expect(coder.emojiFlags([0, -90])).toStrictEqual(['🇦🇶']); 1004 | }); 1005 | 1006 | it('does not code North Pole', () => { 1007 | expect(coder.emojiFlags([0, 90])).toStrictEqual([]); 1008 | }); 1009 | 1010 | it('does not code location in Bir Tawil', () => { 1011 | expect(coder.emojiFlags([33.75, 21.87])).toStrictEqual([]); 1012 | }); 1013 | }); 1014 | 1015 | describe('ccTLDs', () => { 1016 | it('codes locations', () => { 1017 | expect(coder.ccTLDs([-4.5, 54.2])).toStrictEqual(['.im', '.uk']); // not .gb 1018 | expect(coder.ccTLDs([-2.35, 49.43])).toStrictEqual(['.gg', '.uk']); // not .gb 1019 | expect(coder.ccTLDs([-12.3, -37.1])).toStrictEqual(['.ta', '.sh', '.uk']); // not .gb 1020 | expect(coder.ccTLDs([12.59, 55.68])).toStrictEqual(['.dk', '.eu']); 1021 | expect(coder.ccTLDs([2.35, 48.85])).toStrictEqual(['.fx', '.fr', '.eu']); 1022 | expect(coder.ccTLDs([-74, 40.6])).toStrictEqual(['.us']); 1023 | expect(coder.ccTLDs([21, 42.6])).toStrictEqual(['.xk']); 1024 | expect(coder.ccTLDs([0, -90])).toStrictEqual(['.aq']); 1025 | }); 1026 | 1027 | it('codes bounding boxes', () => { 1028 | expect(coder.ccTLDs([-4.5, 54.2, -4.4, 54.3])).toStrictEqual(['.im', '.uk']); // not .gb 1029 | // area of US overlapping Canada's bounding box but not its polygon 1030 | expect(coder.ccTLDs([-74, 40.6, -71.3, 44.7])).toStrictEqual(['.us']); 1031 | // area overlapping both US and Canada 1032 | expect(coder.ccTLDs([-74, 40.6, -71.3, 45])).toStrictEqual(['.ca', '.us']); 1033 | }); 1034 | 1035 | it('does not code invalid arguments', () => { 1036 | expect(coder.ccTLDs([] as any)).toStrictEqual([]); 1037 | expect(coder.ccTLDs([-900, 900])).toStrictEqual([]); 1038 | }); 1039 | 1040 | it('does not code North Pole', () => { 1041 | expect(coder.ccTLDs([0, 90])).toStrictEqual([]); 1042 | expect(coder.ccTLDs([-0.1, 89.9, 0, 90])).toStrictEqual([]); 1043 | }); 1044 | 1045 | it('does not code location in Bir Tawil', () => { 1046 | expect(coder.ccTLDs([33.75, 21.87])).toStrictEqual([]); 1047 | }); 1048 | }); 1049 | 1050 | describe('featuresContaining', () => { 1051 | describe('by location', () => { 1052 | it('codes location in officially-assigned country: New York, United States', () => { 1053 | let features = coder.featuresContaining([-74, 40.6]); 1054 | expect(features.length).toBe(8); 1055 | expect(features[0].properties.nameEn).toBe('Contiguous United States'); 1056 | expect(features[1].properties.nameEn).toBe('US States'); 1057 | expect(features[2].properties.iso1A2).toBe('US'); 1058 | expect(features[3].properties.m49).toBe('021'); 1059 | expect(features[4].properties.m49).toBe('003'); 1060 | expect(features[5].properties.m49).toBe('019'); 1061 | expect(features[6].properties.iso1A2).toBe('UN'); 1062 | expect(features[7].properties.m49).toBe('001'); 1063 | }); 1064 | 1065 | it('codes location in officially-assigned country: New York, United States, strict', () => { 1066 | let features = coder.featuresContaining([-74, 40.6], true); 1067 | expect(features.length).toBe(8); 1068 | expect(features[0].properties.nameEn).toBe('Contiguous United States'); 1069 | expect(features[1].properties.nameEn).toBe('US States'); 1070 | expect(features[2].properties.iso1A2).toBe('US'); 1071 | expect(features[3].properties.m49).toBe('021'); 1072 | expect(features[4].properties.m49).toBe('003'); 1073 | expect(features[5].properties.m49).toBe('019'); 1074 | expect(features[6].properties.iso1A2).toBe('UN'); 1075 | expect(features[7].properties.m49).toBe('001'); 1076 | }); 1077 | 1078 | it('codes location in officially-assigned country, outside but surrounded by EU: Geneva, Switzerland', () => { 1079 | let features = coder.featuresContaining([6.1, 46.2]); 1080 | expect(features.length).toBe(5); 1081 | expect(features[0].properties.iso1A2).toBe('CH'); 1082 | expect(features[1].properties.m49).toBe('155'); 1083 | expect(features[2].properties.m49).toBe('150'); 1084 | expect(features[3].properties.iso1A2).toBe('UN'); 1085 | expect(features[4].properties.m49).toBe('001'); 1086 | }); 1087 | 1088 | it('codes location in officially-assigned country, in EU, outside Eurozone: Copenhagen, Denmark', () => { 1089 | let features = coder.featuresContaining([12.59, 55.68]); 1090 | expect(features.length).toBe(7); 1091 | expect(features[0].properties.wikidata).toBe('Q35'); 1092 | expect(features[1].properties.iso1A2).toBe('DK'); 1093 | expect(features[2].properties.m49).toBe('154'); 1094 | expect(features[3].properties.m49).toBe('150'); 1095 | expect(features[4].properties.iso1A2).toBe('EU'); 1096 | expect(features[5].properties.iso1A2).toBe('UN'); 1097 | expect(features[6].properties.m49).toBe('001'); 1098 | }); 1099 | 1100 | it('codes location in officially-assigned country, in EU, in Eurozone: Berlin, Germany', () => { 1101 | let features = coder.featuresContaining([13.4, 52.5]); 1102 | expect(features.length).toBe(6); 1103 | expect(features[0].properties.iso1A2).toBe('DE'); 1104 | expect(features[1].properties.m49).toBe('155'); 1105 | expect(features[2].properties.m49).toBe('150'); 1106 | expect(features[3].properties.iso1A2).toBe('EU'); 1107 | expect(features[4].properties.iso1A2).toBe('UN'); 1108 | expect(features[5].properties.m49).toBe('001'); 1109 | }); 1110 | 1111 | it('codes location in officially-assigned subfeature, outside EU, of officially-assigned country, in EU: Isle of Man, United Kingdom', () => { 1112 | let features = coder.featuresContaining([-4.5, 54.2]); 1113 | expect(features.length).toBe(7); 1114 | expect(features[0].properties.iso1A2).toBe('IM'); 1115 | expect(features[1].properties.wikidata).toBe('Q185086'); 1116 | expect(features[2].properties.iso1A2).toBe('GB'); 1117 | expect(features[3].properties.m49).toBe('154'); 1118 | expect(features[4].properties.m49).toBe('150'); 1119 | expect(features[5].properties.iso1A2).toBe('UN'); 1120 | expect(features[6].properties.m49).toBe('001'); 1121 | }); 1122 | 1123 | it('codes location in England, United Kingdom', () => { 1124 | let features = coder.featuresContaining([0, 51.5]); 1125 | expect(features.length).toBe(8); 1126 | expect(features[0].properties.nameEn).toBe('England'); 1127 | expect(features[1].properties.nameEn).toBe('Countries of the United Kingdom'); 1128 | expect(features[2].properties.iso1A2).toBe('GB'); 1129 | expect(features[3].properties.nameEn).toBe('Great Britain'); 1130 | expect(features[4].properties.m49).toBe('154'); 1131 | expect(features[5].properties.m49).toBe('150'); 1132 | expect(features[6].properties.iso1A2).toBe('UN'); 1133 | expect(features[7].properties.m49).toBe('001'); 1134 | }); 1135 | 1136 | it('codes location in exceptionally-reserved subfeature of officially-assigned country, in EU, in Eurozone: Paris, Metropolitan France', () => { 1137 | let features = coder.featuresContaining([2.35, 48.85]); 1138 | expect(features.length).toBe(7); 1139 | expect(features[0].properties.iso1A2).toBe('FX'); 1140 | expect(features[1].properties.iso1A2).toBe('FR'); 1141 | expect(features[2].properties.m49).toBe('155'); 1142 | expect(features[3].properties.m49).toBe('150'); 1143 | expect(features[4].properties.iso1A2).toBe('EU'); 1144 | expect(features[5].properties.iso1A2).toBe('UN'); 1145 | expect(features[6].properties.m49).toBe('001'); 1146 | }); 1147 | 1148 | it('codes location in exceptionally-reserved subfeature of officially-assigned subfeature, outside EU, of officially-assigned country, in EU: Tristan da Cunha, SH, UK', () => { 1149 | let features = coder.featuresContaining([-12.3, -37.1]); 1150 | expect(features.length).toBe(9); 1151 | expect(features[0].properties.iso1A2).toBe('TA'); 1152 | expect(features[1].properties.iso1A2).toBe('SH'); 1153 | expect(features[2].properties.wikidata).toBe('Q46395'); 1154 | expect(features[3].properties.iso1A2).toBe('GB'); 1155 | expect(features[4].properties.m49).toBe('011'); 1156 | expect(features[5].properties.m49).toBe('202'); 1157 | expect(features[6].properties.m49).toBe('002'); 1158 | expect(features[7].properties.iso1A2).toBe('UN'); 1159 | expect(features[8].properties.m49).toBe('001'); 1160 | }); 1161 | 1162 | it('codes location in user-assigned, de facto country: Kosovo', () => { 1163 | let features = coder.featuresContaining([21, 42.6]); 1164 | expect(features.length).toBe(4); 1165 | expect(features[0].properties.iso1A2).toBe('XK'); 1166 | expect(features[1].properties.m49).toBe('039'); 1167 | expect(features[2].properties.m49).toBe('150'); 1168 | expect(features[3].properties.m49).toBe('001'); 1169 | }); 1170 | 1171 | it('codes location in exclave of officially-assigned country: Sokh District, Uzbekistan', () => { 1172 | let features = coder.featuresContaining([71.13, 39.96]); 1173 | expect(features.length).toBe(5); 1174 | expect(features[0].properties.iso1A2).toBe('UZ'); 1175 | expect(features[1].properties.m49).toBe('143'); 1176 | expect(features[2].properties.m49).toBe('142'); 1177 | expect(features[3].properties.iso1A2).toBe('UN'); 1178 | expect(features[4].properties.m49).toBe('001'); 1179 | }); 1180 | 1181 | it('codes South Pole as AQ', () => { 1182 | let features = coder.featuresContaining([0, -90]); 1183 | expect(features.length).toBe(2); 1184 | expect(features[0].properties.iso1A2).toBe('AQ'); 1185 | expect(features[1].properties.m49).toBe('001'); 1186 | }); 1187 | 1188 | it('does not code North Pole', () => { 1189 | expect(coder.featuresContaining([0, 90])).toStrictEqual([]); 1190 | }); 1191 | }); 1192 | describe('by code', () => { 1193 | it('codes US', () => { 1194 | let features = coder.featuresContaining('US'); 1195 | expect(features.length).toBe(3); 1196 | expect(features[0].properties.iso1A2).toBe('US'); 1197 | expect(features[1].properties.iso1A2).toBe('UN'); 1198 | expect(features[2].properties.m49).toBe('001'); 1199 | }); 1200 | 1201 | it('codes US, strict', () => { 1202 | let features = coder.featuresContaining('US', true); 1203 | expect(features.length).toBe(2); 1204 | expect(features[0].properties.iso1A2).toBe('UN'); 1205 | expect(features[1].properties.m49).toBe('001'); 1206 | }); 1207 | 1208 | it('codes CONUS', () => { 1209 | let features = coder.featuresContaining('CONUS'); 1210 | expect(features.length).toBe(8); 1211 | expect(features[0].properties.nameEn).toBe('Contiguous United States'); 1212 | expect(features[1].properties.nameEn).toBe('US States'); 1213 | expect(features[2].properties.iso1A2).toBe('US'); 1214 | expect(features[3].properties.m49).toBe('021'); 1215 | expect(features[4].properties.m49).toBe('003'); 1216 | expect(features[5].properties.m49).toBe('019'); 1217 | expect(features[6].properties.iso1A2).toBe('UN'); 1218 | expect(features[7].properties.m49).toBe('001'); 1219 | }); 1220 | 1221 | it('codes CONUS, strict', () => { 1222 | let features = coder.featuresContaining('CONUS', true); 1223 | expect(features.length).toBe(7); 1224 | expect(features[0].properties.nameEn).toBe('US States'); 1225 | expect(features[1].properties.iso1A2).toBe('US'); 1226 | expect(features[2].properties.m49).toBe('021'); 1227 | expect(features[3].properties.m49).toBe('003'); 1228 | expect(features[4].properties.m49).toBe('019'); 1229 | expect(features[5].properties.iso1A2).toBe('UN'); 1230 | expect(features[6].properties.m49).toBe('001'); 1231 | }); 1232 | 1233 | it('codes CH', () => { 1234 | let features = coder.featuresContaining('CH'); 1235 | expect(features.length).toBe(5); 1236 | expect(features[0].properties.iso1A2).toBe('CH'); 1237 | expect(features[1].properties.m49).toBe('155'); 1238 | expect(features[2].properties.m49).toBe('150'); 1239 | expect(features[3].properties.iso1A2).toBe('UN'); 1240 | expect(features[4].properties.m49).toBe('001'); 1241 | }); 1242 | 1243 | it('codes DK', () => { 1244 | let features = coder.featuresContaining('DK'); 1245 | expect(features.length).toBe(3); 1246 | expect(features[0].properties.iso1A2).toBe('DK'); 1247 | expect(features[1].properties.iso1A2).toBe('UN'); 1248 | expect(features[2].properties.m49).toBe('001'); 1249 | }); 1250 | 1251 | it('codes DE', () => { 1252 | let features = coder.featuresContaining('DE'); 1253 | expect(features.length).toBe(6); 1254 | expect(features[0].properties.iso1A2).toBe('DE'); 1255 | expect(features[1].properties.m49).toBe('155'); 1256 | expect(features[2].properties.m49).toBe('150'); 1257 | expect(features[3].properties.iso1A2).toBe('EU'); 1258 | expect(features[4].properties.iso1A2).toBe('UN'); 1259 | expect(features[5].properties.m49).toBe('001'); 1260 | }); 1261 | 1262 | it('codes IM', () => { 1263 | let features = coder.featuresContaining('IM'); 1264 | expect(features.length).toBe(7); 1265 | expect(features[0].properties.iso1A2).toBe('IM'); 1266 | expect(features[1].properties.wikidata).toBe('Q185086'); 1267 | expect(features[2].properties.iso1A2).toBe('GB'); 1268 | expect(features[3].properties.m49).toBe('154'); 1269 | expect(features[4].properties.m49).toBe('150'); 1270 | expect(features[5].properties.iso1A2).toBe('UN'); 1271 | expect(features[6].properties.m49).toBe('001'); 1272 | }); 1273 | 1274 | it('codes GB', () => { 1275 | let features = coder.featuresContaining('GB'); 1276 | expect(features.length).toBe(3); 1277 | expect(features[0].properties.iso1A2).toBe('GB'); 1278 | expect(features[1].properties.iso1A2).toBe('UN'); 1279 | expect(features[2].properties.m49).toBe('001'); 1280 | }); 1281 | 1282 | it('codes FX', () => { 1283 | let features = coder.featuresContaining('FX'); 1284 | expect(features.length).toBe(7); 1285 | expect(features[0].properties.iso1A2).toBe('FX'); 1286 | expect(features[1].properties.iso1A2).toBe('FR'); 1287 | expect(features[2].properties.m49).toBe('155'); 1288 | expect(features[3].properties.m49).toBe('150'); 1289 | expect(features[4].properties.iso1A2).toBe('EU'); 1290 | expect(features[5].properties.iso1A2).toBe('UN'); 1291 | expect(features[6].properties.m49).toBe('001'); 1292 | }); 1293 | 1294 | it('codes TA', () => { 1295 | let features = coder.featuresContaining('TA'); 1296 | expect(features.length).toBe(9); 1297 | expect(features[0].properties.iso1A2).toBe('TA'); 1298 | expect(features[1].properties.iso1A2).toBe('SH'); 1299 | expect(features[2].properties.wikidata).toBe('Q46395'); 1300 | expect(features[3].properties.iso1A2).toBe('GB'); 1301 | expect(features[4].properties.m49).toBe('011'); 1302 | expect(features[5].properties.m49).toBe('202'); 1303 | expect(features[6].properties.m49).toBe('002'); 1304 | expect(features[7].properties.iso1A2).toBe('UN'); 1305 | expect(features[8].properties.m49).toBe('001'); 1306 | }); 1307 | 1308 | it('codes XK', () => { 1309 | let features = coder.featuresContaining('XK'); 1310 | expect(features.length).toBe(4); 1311 | expect(features[0].properties.iso1A2).toBe('XK'); 1312 | expect(features[1].properties.m49).toBe('039'); 1313 | expect(features[2].properties.m49).toBe('150'); 1314 | expect(features[3].properties.m49).toBe('001'); 1315 | }); 1316 | 1317 | it('codes UZ', () => { 1318 | let features = coder.featuresContaining('UZ'); 1319 | expect(features.length).toBe(5); 1320 | expect(features[0].properties.iso1A2).toBe('UZ'); 1321 | expect(features[1].properties.m49).toBe('143'); 1322 | expect(features[2].properties.m49).toBe('142'); 1323 | expect(features[3].properties.iso1A2).toBe('UN'); 1324 | expect(features[4].properties.m49).toBe('001'); 1325 | }); 1326 | 1327 | it('codes AQ', () => { 1328 | let features = coder.featuresContaining('AQ'); 1329 | expect(features.length).toBe(2); 1330 | expect(features[0].properties.iso1A2).toBe('AQ'); 1331 | expect(features[1].properties.m49).toBe('001'); 1332 | }); 1333 | }); 1334 | }); 1335 | 1336 | describe('featuresIn', () => { 1337 | it('codes CN', () => { 1338 | let features = coder.featuresIn('CN'); 1339 | expect(features.length).toBe(4); 1340 | expect(features[0].properties.iso1A2).toBe('CN'); 1341 | expect(features[1].properties.wikidata).toBe('Q19188'); 1342 | expect(features[2].properties.iso1A2).toBe('HK'); 1343 | expect(features[3].properties.iso1A2).toBe('MO'); 1344 | }); 1345 | 1346 | it('codes CN, strict', () => { 1347 | let features = coder.featuresIn('CN', true); 1348 | expect(features.length).toBe(3); 1349 | expect(features[0].properties.wikidata).toBe('Q19188'); 1350 | expect(features[1].properties.iso1A2).toBe('HK'); 1351 | expect(features[2].properties.iso1A2).toBe('MO'); 1352 | }); 1353 | 1354 | it('codes 830', () => { 1355 | let features = coder.featuresIn(830); 1356 | expect(features.length).toBe(6); 1357 | expect(features[0].properties.m49).toBe('830'); 1358 | expect(features[1].properties.wikidata).toBe('Q179313'); 1359 | expect(features[2].properties.wikidata).toBe('Q3311985'); 1360 | expect(features[3].properties.m49).toBe('680'); 1361 | expect(features[4].properties.iso1A2).toBe('GG'); 1362 | expect(features[5].properties.iso1A2).toBe('JE'); 1363 | }); 1364 | 1365 | it('codes 830, strict', () => { 1366 | let features = coder.featuresIn(830, true); 1367 | expect(features.length).toBe(5); 1368 | expect(features[0].properties.wikidata).toBe('Q179313'); 1369 | expect(features[1].properties.wikidata).toBe('Q3311985'); 1370 | expect(features[2].properties.m49).toBe('680'); 1371 | expect(features[3].properties.iso1A2).toBe('GG'); 1372 | expect(features[4].properties.iso1A2).toBe('JE'); 1373 | }); 1374 | 1375 | it('codes "Crown Dependencies", strict', () => { 1376 | let features = coder.featuresIn('Crown Dependencies', true); 1377 | expect(features.length).toBe(6); 1378 | expect(features[0].properties.wikidata).toBe('Q179313'); 1379 | expect(features[1].properties.wikidata).toBe('Q3311985'); 1380 | expect(features[2].properties.m49).toBe('680'); 1381 | expect(features[3].properties.iso1A2).toBe('GG'); 1382 | expect(features[4].properties.iso1A2).toBe('IM'); 1383 | expect(features[5].properties.iso1A2).toBe('JE'); 1384 | }); 1385 | 1386 | it('codes "SBA", strict', () => { 1387 | let features = coder.featuresIn('SBA', true); 1388 | expect(features.length).toBe(2); 1389 | expect(features[0].properties.wikidata).toBe('Q9143535'); 1390 | expect(features[1].properties.wikidata).toBe('Q9206745'); 1391 | }); 1392 | 1393 | it('codes 🇸🇭 (Saint Helena)', () => { 1394 | let features = coder.featuresIn('🇸🇭'); 1395 | expect(features.length).toBe(4); 1396 | expect(features[0].properties.iso1A2).toBe('SH'); 1397 | expect(features[1].properties.wikidata).toBe('Q34497'); 1398 | expect(features[2].properties.iso1A2).toBe('AC'); 1399 | expect(features[3].properties.iso1A2).toBe('TA'); 1400 | }); 1401 | 1402 | it('codes 🇸🇭 (Saint Helena), strict', () => { 1403 | let features = coder.featuresIn('🇸🇭', true); 1404 | expect(features.length).toBe(3); 1405 | expect(features[0].properties.wikidata).toBe('Q34497'); 1406 | expect(features[1].properties.iso1A2).toBe('AC'); 1407 | expect(features[2].properties.iso1A2).toBe('TA'); 1408 | }); 1409 | 1410 | it('codes UN', () => { 1411 | let features = coder.featuresIn('UN').filter(function (feature) { 1412 | return feature.properties.level === 'country'; 1413 | }); 1414 | // there are exactly 193 UN member states as of August 2020 1415 | expect(features.length).toBe(193); 1416 | }); 1417 | 1418 | it('codes AQ', () => { 1419 | let features = coder.featuresIn('AQ'); 1420 | expect(features.length).toBe(1); 1421 | expect(features[0].properties.iso1A2).toBe('AQ'); 1422 | }); 1423 | 1424 | it('codes AQ, strict', () => { 1425 | expect(coder.featuresIn('AQ', true)).toStrictEqual([]); 1426 | }); 1427 | 1428 | it('does not code ABC', () => { 1429 | expect(coder.featuresIn('ABC')).toStrictEqual([]); 1430 | }); 1431 | }); 1432 | 1433 | describe('aggregateFeature', () => { 1434 | it('returns aggregate for feature with geometry', () => { 1435 | expect(coder.aggregateFeature('TA')?.geometry.coordinates.length).toBe(1); 1436 | }); 1437 | it('returns aggregate for feature without geometry', () => { 1438 | expect(coder.aggregateFeature('CN')?.geometry.coordinates.length).toBe(3); 1439 | expect(coder.aggregateFeature('SH')?.geometry.coordinates.length).toBe(3); 1440 | expect(coder.aggregateFeature('EU')?.geometry.coordinates.length).toBe(50); 1441 | }); 1442 | it('returns null for invalid ID', () => { 1443 | expect(coder.aggregateFeature('ABC')).toBeNull(); 1444 | }); 1445 | }); 1446 | describe('isIn', () => { 1447 | describe('by location', () => { 1448 | it('returns true: US location in US', () => { 1449 | expect(coder.isIn([-74, 40.6], 'US')).toBe(true); 1450 | }); 1451 | it('returns false: US location in CH', () => { 1452 | expect(coder.isIn([-74, 40.6], 'CH')).toBe(false); 1453 | }); 1454 | it('returns true: US location in 19 (Americas)', () => { 1455 | expect(coder.isIn([-74, 40.6], 19)).toBe(true); 1456 | }); 1457 | it('returns true: US location in "021" (Northern America)', () => { 1458 | expect(coder.isIn([-74, 40.6], '021')).toBe(true); 1459 | }); 1460 | it('returns true: US location in Q2 (World)', () => { 1461 | expect(coder.isIn([-74, 40.6], 'Q2')).toBe(true); 1462 | }); 1463 | it('returns false: US location in Q15 (Africa)', () => { 1464 | expect(coder.isIn([-74, 40.6], 'Q15')).toBe(false); 1465 | }); 1466 | }); 1467 | 1468 | describe('by code', () => { 1469 | it('returns true: US in US', () => { 1470 | expect(coder.isIn('US', 'US')).toBe(true); 1471 | }); 1472 | it('returns false: US in CH', () => { 1473 | expect(coder.isIn('US', 'CH')).toBe(false); 1474 | }); 1475 | it('returns false: USA in 19 (Americas)', () => { 1476 | expect(coder.isIn('USA', 19)).toBe(false); 1477 | }); 1478 | it('returns false: US in "021" (Northern America)', () => { 1479 | expect(coder.isIn('US', '021')).toBe(false); 1480 | }); 1481 | it('returns false: US location in Q15 (Africa)', () => { 1482 | expect(coder.isIn('US', 'Q15')).toBe(false); 1483 | }); 1484 | it('returns true: CONUS in 19 (Americas)', () => { 1485 | expect(coder.isIn('CONUS', 19)).toBe(true); 1486 | }); 1487 | it('returns true: CONUS in "021" (Northern America)', () => { 1488 | expect(coder.isIn('CONUS', '021')).toBe(true); 1489 | }); 1490 | it('returns true: PR in US', () => { 1491 | expect(coder.isIn('PR', 'US')).toBe(true); 1492 | }); 1493 | it('returns false: US in PR', () => { 1494 | expect(coder.isIn('US', 'PR')).toBe(false); 1495 | }); 1496 | it('returns true: TA in SH', () => { 1497 | expect(coder.isIn('TA', 'SH')).toBe(true); 1498 | }); 1499 | it('returns true: TA in GB', () => { 1500 | expect(coder.isIn('TA', 'GB')).toBe(true); 1501 | }); 1502 | it('returns false: TA in EU', () => { 1503 | expect(coder.isIn('TA', 'EU')).toBe(false); 1504 | }); 1505 | it('returns true: MP in "Q153732"', () => { 1506 | expect(coder.isIn('MP', 'Q153732')).toBe(true); 1507 | }); 1508 | it('returns true: "Navassa Island" in "UM"', () => { 1509 | expect(coder.isIn('Navassa Island', 'UM')).toBe(true); 1510 | }); 1511 | it('returns true: "Navassa Island" in "029" (Caribbean)', () => { 1512 | expect(coder.isIn('Navassa Island', '029')).toBe(true); 1513 | }); 1514 | it('returns false: "UM" in "029"', () => { 1515 | expect(coder.isIn('UM', '029')).toBe(false); 1516 | }); 1517 | it('returns true: "Midway Atoll" in "UM"', () => { 1518 | expect(coder.isIn('Midway Atoll', 'UM')).toBe(true); 1519 | }); 1520 | it('returns true: "Midway Atoll" in "US"', () => { 1521 | expect(coder.isIn('Midway Atoll', 'US')).toBe(true); 1522 | }); 1523 | it('returns false: "Midway Atoll" in "Hawaii"', () => { 1524 | expect(coder.isIn('Midway Atoll', 'Hawaii')).toBe(false); 1525 | }); 1526 | it('returns true: GU in "Q153732" (Mariana Islands)', () => { 1527 | expect(coder.isIn('GU', 'Q153732')).toBe(true); 1528 | }); 1529 | it('returns true: "GU" in US', () => { 1530 | expect(coder.isIn('GU', 'US')).toBe(true); 1531 | }); 1532 | it('returns true: "Alaska" in "US"', () => { 1533 | expect(coder.isIn('Alaska', 'US')).toBe(true); 1534 | }); 1535 | it('returns true: "Hawaii" in "US"', () => { 1536 | expect(coder.isIn('Hawaii', 'US')).toBe(true); 1537 | }); 1538 | it('returns true: "CONUS" in "US"', () => { 1539 | expect(coder.isIn('CONUS', 'US')).toBe(true); 1540 | }); 1541 | it('returns false: "US" in "CONUS"', () => { 1542 | expect(coder.isIn('US', 'CONUS')).toBe(false); 1543 | }); 1544 | it('returns false: "Q153732" in "019" (Mariana Islands in Americas)', () => { 1545 | expect(coder.isIn('Q153732', '019')).toBe(false); 1546 | }); 1547 | it('returns false: "Hawaii" in "019"', () => { 1548 | expect(coder.isIn('Hawaii', '019')).toBe(false); 1549 | }); 1550 | it('returns true: "Alaska" in "019"', () => { 1551 | expect(coder.isIn('Alaska', '019')).toBe(true); 1552 | }); 1553 | it('returns true: "021" in "019" (Northern America in Americas)', () => { 1554 | expect(coder.isIn('021', '019')).toBe(true); 1555 | }); 1556 | it('returns true: "XK" in "europe"', () => { 1557 | expect(coder.isIn('XK', 'europe')).toBe(true); 1558 | }); 1559 | it('returns true: "TW" in "Asia"', () => { 1560 | expect(coder.isIn('TW', 'Asia')).toBe(true); 1561 | }); 1562 | it('returns true: 🇵🇷 in 🇺🇸', () => { 1563 | expect(coder.isIn('🇵🇷', '🇺🇸')).toBe(true); 1564 | }); 1565 | it('returns true: "Bir Tawil" in "015"', () => { 1566 | expect(coder.isIn('Bir Tawil', '015')).toBe(true); 1567 | }); 1568 | it('returns false: "Bir Tawil" in "Sudan"', () => { 1569 | expect(coder.isIn('Bir Tawil', 'Sudan')).toBe(false); 1570 | }); 1571 | it('returns false: "Bir Tawil" in "Egypt"', () => { 1572 | expect(coder.isIn('Bir Tawil', 'Egypt')).toBe(false); 1573 | }); 1574 | it('returns true: "Subsaharan africa" in "AFRICA"', () => { 1575 | expect(coder.isIn('Subsaharan africa', 'AFRICA')).toBe(true); 1576 | }); 1577 | it('returns true: "Africa" in "World"', () => { 1578 | expect(coder.isIn('Africa', 'World')).toBe(true); 1579 | }); 1580 | }); 1581 | }); 1582 | 1583 | describe('isInEuropeanUnion', () => { 1584 | describe('by location', () => { 1585 | it('returns false for location in officially-assigned country, outside EU: New York, United States', () => { 1586 | expect(coder.isInEuropeanUnion([-74, 40.6])).toBe(false); 1587 | }); 1588 | 1589 | it('returns false for location in officially-assigned country, outside but surrounded by EU: Geneva, Switzerland', () => { 1590 | expect(coder.isInEuropeanUnion([6.1, 46.2])).toBe(false); 1591 | }); 1592 | 1593 | it('returns true for location in officially-assigned country, in EU, outside Eurozone: Copenhagen, Denmark', () => { 1594 | expect(coder.isInEuropeanUnion([12.59, 55.68])).toBe(true); 1595 | }); 1596 | 1597 | it('returns true for location in officially-assigned country, in EU, in Eurozone: Berlin, Germany', () => { 1598 | expect(coder.isInEuropeanUnion([13.4, 52.5])).toBe(true); 1599 | }); 1600 | 1601 | it('returns false for location in officially-assigned subfeature, oustide EU, of officially-assigned country, in EU: Isle of Man, United Kingdom', () => { 1602 | expect(coder.isInEuropeanUnion([-4.5, 54.2])).toBe(false); 1603 | }); 1604 | 1605 | it('returns true for location in exceptionally-reserved subfeature, in EU: Paris, Metropolitan France', () => { 1606 | expect(coder.isInEuropeanUnion([2.35, 48.85])).toBe(true); 1607 | }); 1608 | 1609 | it('returns false for location in exceptionally-reserved subfeature of officially-assigned subfeature, outside EU, of officially-assigned country, in EU: Tristan da Cunha, SH, UK', () => { 1610 | expect(coder.isInEuropeanUnion([-12.3, -37.1])).toBe(false); 1611 | }); 1612 | 1613 | it('returns false for location in user-assigned, de facto country, in Europe, outside EU: Kosovo', () => { 1614 | expect(coder.isInEuropeanUnion([21, 42.6])).toBe(false); 1615 | }); 1616 | it('returns true for GeoJSON point feature in Germany', () => { 1617 | let pointFeature = { 1618 | type: 'Feature', 1619 | properties: null, 1620 | geometry: { 1621 | type: 'Point', 1622 | coordinates: tuple(13.4, 52.5) 1623 | } 1624 | }; 1625 | expect(coder.isInEuropeanUnion(pointFeature)).toBe(true); 1626 | }); 1627 | it('returns true for GeoJSON point geometry in Germany', () => { 1628 | let pointGeometry = { 1629 | type: 'Point', 1630 | coordinates: tuple(13.4, 52.5) 1631 | }; 1632 | expect(coder.isInEuropeanUnion(pointGeometry)).toBe(true); 1633 | }); 1634 | }); 1635 | describe('by code', () => { 1636 | it('returns true for European Union itself: EU', () => { 1637 | expect(coder.isInEuropeanUnion('EU')).toBe(true); 1638 | }); 1639 | 1640 | it('returns true for countries inside the EU', () => { 1641 | expect(coder.isInEuropeanUnion('SE')).toBe(true); 1642 | expect(coder.isInEuropeanUnion('DE')).toBe(true); 1643 | expect(coder.isInEuropeanUnion('FX')).toBe(true); 1644 | expect(coder.isInEuropeanUnion('CY')).toBe(true); 1645 | }); 1646 | 1647 | it('returns true for certain territories of EU countries that are inside the EU', () => { 1648 | // Outermost regions 1649 | expect(coder.isInEuropeanUnion('OMR')).toBe(true); 1650 | expect(coder.isInEuropeanUnion('Q2914565')).toBe(true); 1651 | expect(coder.isInEuropeanUnion('Azores')).toBe(true); 1652 | expect(coder.isInEuropeanUnion('Madeira')).toBe(true); 1653 | expect(coder.isInEuropeanUnion('IC')).toBe(true); 1654 | expect(coder.isInEuropeanUnion('GF')).toBe(true); 1655 | expect(coder.isInEuropeanUnion('GP')).toBe(true); 1656 | expect(coder.isInEuropeanUnion('MQ')).toBe(true); 1657 | expect(coder.isInEuropeanUnion('YT')).toBe(true); 1658 | expect(coder.isInEuropeanUnion('RE')).toBe(true); 1659 | expect(coder.isInEuropeanUnion('MF')).toBe(true); 1660 | // special cases 1661 | expect(coder.isInEuropeanUnion('EA')).toBe(true); 1662 | expect(coder.isInEuropeanUnion('Ceuta')).toBe(true); 1663 | expect(coder.isInEuropeanUnion('Melilla')).toBe(true); 1664 | expect(coder.isInEuropeanUnion('AX')).toBe(true); 1665 | }); 1666 | 1667 | it('returns false for certain territories of EU countries outside of the EU', () => { 1668 | // Overseas countries and territories 1669 | expect(coder.isInEuropeanUnion('OCT')).toBe(false); 1670 | expect(coder.isInEuropeanUnion('Greenland')).toBe(false); 1671 | expect(coder.isInEuropeanUnion('CW')).toBe(false); 1672 | expect(coder.isInEuropeanUnion('Aruba')).toBe(false); 1673 | expect(coder.isInEuropeanUnion('SX')).toBe(false); 1674 | expect(coder.isInEuropeanUnion('BQ')).toBe(false); 1675 | expect(coder.isInEuropeanUnion('Bonaire')).toBe(false); 1676 | expect(coder.isInEuropeanUnion('Sint Eustatius')).toBe(false); 1677 | expect(coder.isInEuropeanUnion('Saba')).toBe(false); 1678 | expect(coder.isInEuropeanUnion('PF')).toBe(false); 1679 | expect(coder.isInEuropeanUnion('NC')).toBe(false); 1680 | expect(coder.isInEuropeanUnion('WF')).toBe(false); 1681 | expect(coder.isInEuropeanUnion('BL')).toBe(false); 1682 | expect(coder.isInEuropeanUnion('TF')).toBe(false); 1683 | // special case 1684 | expect(coder.isInEuropeanUnion('FO')).toBe(false); 1685 | }); 1686 | 1687 | it('returns false for countries outside the EU', () => { 1688 | expect(coder.isInEuropeanUnion('US')).toBe(false); 1689 | expect(coder.isInEuropeanUnion('RU')).toBe(false); 1690 | expect(coder.isInEuropeanUnion('NO')).toBe(false); 1691 | expect(coder.isInEuropeanUnion('CH')).toBe(false); 1692 | expect(coder.isInEuropeanUnion('CN')).toBe(false); 1693 | expect(coder.isInEuropeanUnion('XK')).toBe(false); 1694 | }); 1695 | 1696 | it('returns false for territories outside the EU', () => { 1697 | expect(coder.isInEuropeanUnion('IM')).toBe(false); 1698 | expect(coder.isInEuropeanUnion('TA')).toBe(false); 1699 | expect(coder.isInEuropeanUnion('HK')).toBe(false); 1700 | expect(coder.isInEuropeanUnion('VI')).toBe(false); 1701 | }); 1702 | 1703 | it('returns false for M49 super-region code: 150', () => { 1704 | expect(coder.isInEuropeanUnion('150')).toBe(false); 1705 | }); 1706 | 1707 | it('returns false for "world"', () => { 1708 | expect(coder.isInEuropeanUnion('world')).toBe(false); 1709 | }); 1710 | 1711 | it('returns null for unassigned alpha-2 code: AB', () => { 1712 | expect(coder.isInEuropeanUnion('AB')).toBe(null); 1713 | }); 1714 | 1715 | it('returns null for empty string', () => { 1716 | expect(coder.isInEuropeanUnion('')).toBe(null); 1717 | }); 1718 | }); 1719 | }); 1720 | 1721 | describe('isInUnitedNations', () => { 1722 | describe('by location', () => { 1723 | it('returns for coordinate location:', () => { 1724 | expect(coder.isInUnitedNations([-74, 40.6])).toBe(true); // New York, United States 1725 | expect(coder.isInUnitedNations([6.1, 46.2])).toBe(true); // Geneva, Switzerland 1726 | expect(coder.isInUnitedNations([12.59, 55.68])).toBe(true); // Copenhagen, Denmark 1727 | expect(coder.isInUnitedNations([13.4, 52.5])).toBe(true); // Berlin, Germany 1728 | expect(coder.isInUnitedNations([-4.5, 54.2])).toBe(true); // Isle of Man, United Kingdom 1729 | expect(coder.isInUnitedNations([2.35, 48.85])).toBe(true); // Metropolitan France 1730 | expect(coder.isInUnitedNations([-12.3, -37.1])).toBe(true); // Tristan da Cunha, SH, UK 1731 | expect(coder.isInUnitedNations([21, 42.6])).toBe(false); // Kosovo 1732 | }); 1733 | it('returns for GeoJSON point feature', () => { 1734 | let pointFeature = { 1735 | type: 'Feature', 1736 | properties: null, 1737 | geometry: { 1738 | type: 'Point', 1739 | coordinates: tuple(13.4, 52.5) // Berlin, Germany 1740 | } 1741 | }; 1742 | expect(coder.isInUnitedNations(pointFeature)).toBe(true); 1743 | }); 1744 | it('returns for GeoJSON point geometry', () => { 1745 | let pointGeometry = { 1746 | type: 'Point', 1747 | coordinates: tuple(13.4, 52.5) // Berlin, Germany 1748 | }; 1749 | expect(coder.isInUnitedNations(pointGeometry)).toBe(true); 1750 | }); 1751 | }); 1752 | describe('by code', () => { 1753 | it('returns true for features in UN', () => { 1754 | expect(coder.isInUnitedNations('EU')).toBe(true); 1755 | expect(coder.isInUnitedNations('SE')).toBe(true); 1756 | expect(coder.isInUnitedNations('DE')).toBe(true); 1757 | expect(coder.isInUnitedNations('FX')).toBe(true); 1758 | expect(coder.isInUnitedNations('CY')).toBe(true); 1759 | expect(coder.isInUnitedNations('OMR')).toBe(true); 1760 | expect(coder.isInUnitedNations('Q2914565')).toBe(true); 1761 | expect(coder.isInUnitedNations('Azores')).toBe(true); 1762 | expect(coder.isInUnitedNations('Madeira')).toBe(true); 1763 | expect(coder.isInUnitedNations('IC')).toBe(true); 1764 | expect(coder.isInUnitedNations('GF')).toBe(true); 1765 | expect(coder.isInUnitedNations('GP')).toBe(true); 1766 | expect(coder.isInUnitedNations('MQ')).toBe(true); 1767 | expect(coder.isInUnitedNations('YT')).toBe(true); 1768 | expect(coder.isInUnitedNations('RE')).toBe(true); 1769 | expect(coder.isInUnitedNations('MF')).toBe(true); 1770 | expect(coder.isInUnitedNations('EA')).toBe(true); 1771 | expect(coder.isInUnitedNations('Ceuta')).toBe(true); 1772 | expect(coder.isInUnitedNations('Melilla')).toBe(true); 1773 | expect(coder.isInUnitedNations('AX')).toBe(true); 1774 | expect(coder.isInUnitedNations('OCT')).toBe(true); 1775 | expect(coder.isInUnitedNations('Greenland')).toBe(true); 1776 | expect(coder.isInUnitedNations('CW')).toBe(true); 1777 | expect(coder.isInUnitedNations('Aruba')).toBe(true); 1778 | expect(coder.isInUnitedNations('SX')).toBe(true); 1779 | expect(coder.isInUnitedNations('BQ')).toBe(true); 1780 | expect(coder.isInUnitedNations('Bonaire')).toBe(true); 1781 | expect(coder.isInUnitedNations('Sint Eustatius')).toBe(true); 1782 | expect(coder.isInUnitedNations('Saba')).toBe(true); 1783 | expect(coder.isInUnitedNations('PF')).toBe(true); 1784 | expect(coder.isInUnitedNations('NC')).toBe(true); 1785 | expect(coder.isInUnitedNations('WF')).toBe(true); 1786 | expect(coder.isInUnitedNations('BL')).toBe(true); 1787 | expect(coder.isInUnitedNations('TF')).toBe(true); 1788 | expect(coder.isInUnitedNations('FO')).toBe(true); 1789 | expect(coder.isInUnitedNations('US')).toBe(true); 1790 | expect(coder.isInUnitedNations('RU')).toBe(true); 1791 | expect(coder.isInUnitedNations('NO')).toBe(true); 1792 | expect(coder.isInUnitedNations('CH')).toBe(true); 1793 | expect(coder.isInUnitedNations('CN')).toBe(true); 1794 | expect(coder.isInUnitedNations('IM')).toBe(true); 1795 | expect(coder.isInUnitedNations('TA')).toBe(true); 1796 | expect(coder.isInUnitedNations('HK')).toBe(true); 1797 | expect(coder.isInUnitedNations('VI')).toBe(true); 1798 | }); 1799 | 1800 | it('returns false for features not in UN', () => { 1801 | expect(coder.isInUnitedNations('XK')).toBe(false); 1802 | expect(coder.isInUnitedNations('PS')).toBe(false); 1803 | expect(coder.isInUnitedNations('TW')).toBe(false); 1804 | expect(coder.isInUnitedNations('AQ')).toBe(false); 1805 | expect(coder.isInUnitedNations('VA')).toBe(false); 1806 | expect(coder.isInUnitedNations('Western Sahara')).toBe(false); 1807 | expect(coder.isInUnitedNations('Northern Cyprus')).toBe(false); 1808 | expect(coder.isInUnitedNations('Bir Tawil')).toBe(false); 1809 | expect(coder.isInUnitedNations('150')).toBe(false); 1810 | expect(coder.isInUnitedNations('world')).toBe(false); 1811 | }); 1812 | 1813 | it('returns null for invalid codes', () => { 1814 | expect(coder.isInUnitedNations('AB')).toBe(null); 1815 | expect(coder.isInUnitedNations('')).toBe(null); 1816 | }); 1817 | }); 1818 | }); 1819 | 1820 | describe('driveSide', () => { 1821 | it('finds feature using right by location', () => { 1822 | expect(coder.driveSide([-74, 40.6])).toBe('right'); 1823 | }); 1824 | 1825 | it('finds feature using left by location', () => { 1826 | expect(coder.driveSide([-4.5, 54.2])).toBe('left'); 1827 | }); 1828 | 1829 | it('finds feature using right by identifier', () => { 1830 | expect(coder.driveSide('DE')).toBe('right'); 1831 | expect(coder.driveSide('CA')).toBe('right'); 1832 | expect(coder.driveSide('IO')).toBe('right'); 1833 | expect(coder.driveSide('PR')).toBe('right'); 1834 | expect(coder.driveSide('GI')).toBe('right'); 1835 | expect(coder.driveSide('ES')).toBe('right'); 1836 | expect(coder.driveSide('FR')).toBe('right'); 1837 | expect(coder.driveSide('Midway Atoll')).toBe('right'); 1838 | expect(coder.driveSide('Hawaii')).toBe('right'); 1839 | expect(coder.driveSide('CONUS')).toBe('right'); 1840 | }); 1841 | 1842 | it('finds feature using left by identifier', () => { 1843 | expect(coder.driveSide('VI')).toBe('left'); 1844 | expect(coder.driveSide('GB-SCT')).toBe('left'); 1845 | expect(coder.driveSide('IM')).toBe('left'); 1846 | expect(coder.driveSide('IE')).toBe('left'); 1847 | expect(coder.driveSide('IN')).toBe('left'); 1848 | expect(coder.driveSide('AU')).toBe('left'); 1849 | expect(coder.driveSide('NZ')).toBe('left'); 1850 | expect(coder.driveSide('SH')).toBe('left'); 1851 | expect(coder.driveSide('TA')).toBe('left'); 1852 | expect(coder.driveSide('HMD')).toBe('left'); 1853 | expect(coder.driveSide('JP')).toBe('left'); 1854 | expect(coder.driveSide('ZA')).toBe('left'); 1855 | expect(coder.driveSide('Great Britain')).toBe('left'); 1856 | }); 1857 | 1858 | it('finds null for EU', () => { 1859 | expect(coder.driveSide('EU')).toBeNull(); 1860 | }); 1861 | 1862 | it('finds null for 001', () => { 1863 | expect(coder.driveSide('001')).toBeNull(); 1864 | }); 1865 | 1866 | it('finds null for North Pole', () => { 1867 | expect(coder.driveSide([0, 90])).toBeNull(); 1868 | }); 1869 | }); 1870 | 1871 | describe('roadSpeedUnit', () => { 1872 | it('finds feature using km/h by location', () => { 1873 | expect(coder.roadSpeedUnit([2.35, 48.85])).toBe('km/h'); 1874 | }); 1875 | 1876 | it('finds feature using mph by location', () => { 1877 | expect(coder.roadSpeedUnit([-74, 40.6])).toBe('mph'); 1878 | }); 1879 | 1880 | it('finds feature using km/h by identifier', () => { 1881 | expect(coder.roadSpeedUnit('IO')).toBe('km/h'); 1882 | expect(coder.roadSpeedUnit('IE')).toBe('km/h'); 1883 | expect(coder.roadSpeedUnit('AU')).toBe('km/h'); 1884 | expect(coder.roadSpeedUnit('NZ')).toBe('km/h'); 1885 | expect(coder.roadSpeedUnit('ES')).toBe('km/h'); 1886 | expect(coder.roadSpeedUnit('TK')).toBe('km/h'); 1887 | expect(coder.roadSpeedUnit('GI')).toBe('km/h'); 1888 | expect(coder.roadSpeedUnit('FR')).toBe('km/h'); 1889 | }); 1890 | 1891 | it('finds feature using mph by identifier', () => { 1892 | expect(coder.roadSpeedUnit('US')).toBe('mph'); 1893 | expect(coder.roadSpeedUnit('CONUS')).toBe('mph'); 1894 | expect(coder.roadSpeedUnit('US-AK')).toBe('mph'); 1895 | expect(coder.roadSpeedUnit('Midway Atoll')).toBe('mph'); 1896 | expect(coder.roadSpeedUnit('VI')).toBe('mph'); 1897 | expect(coder.roadSpeedUnit('VG')).toBe('mph'); 1898 | expect(coder.roadSpeedUnit('IM')).toBe('mph'); 1899 | expect(coder.roadSpeedUnit('GB-ENG')).toBe('mph'); 1900 | expect(coder.roadSpeedUnit('Great Britain')).toBe('mph'); 1901 | }); 1902 | 1903 | it('finds null for 001', () => { 1904 | expect(coder.roadSpeedUnit('001')).toBeNull(); 1905 | }); 1906 | 1907 | it('finds null for location of North Pole', () => { 1908 | expect(coder.roadSpeedUnit([0, 90])).toBeNull(); 1909 | }); 1910 | }); 1911 | 1912 | describe('roadHeightUnit', () => { 1913 | it('finds feature using m by location', () => { 1914 | expect(coder.roadHeightUnit([2.35, 48.85])).toBe('m'); 1915 | }); 1916 | 1917 | it('finds feature using ft by location', () => { 1918 | expect(coder.roadHeightUnit([-74, 40.6])).toBe('ft'); 1919 | }); 1920 | 1921 | it('finds feature using m by identifier', () => { 1922 | expect(coder.roadHeightUnit('IO')).toBe('m'); 1923 | expect(coder.roadHeightUnit('IE')).toBe('m'); 1924 | expect(coder.roadHeightUnit('AU')).toBe('m'); 1925 | expect(coder.roadHeightUnit('NZ')).toBe('m'); 1926 | expect(coder.roadHeightUnit('ES')).toBe('m'); 1927 | expect(coder.roadHeightUnit('TK')).toBe('m'); 1928 | expect(coder.roadHeightUnit('GI')).toBe('m'); 1929 | expect(coder.roadHeightUnit('FR')).toBe('m'); 1930 | expect(coder.roadHeightUnit('PR')).toBe('m'); 1931 | expect(coder.roadHeightUnit('US-PR')).toBe('m'); 1932 | }); 1933 | 1934 | it('finds feature using ft by identifier', () => { 1935 | expect(coder.roadHeightUnit('CONUS')).toBe('ft'); 1936 | expect(coder.roadHeightUnit('US-AK')).toBe('ft'); 1937 | expect(coder.roadHeightUnit('Midway Atoll')).toBe('ft'); 1938 | expect(coder.roadHeightUnit('VI')).toBe('ft'); 1939 | expect(coder.roadHeightUnit('VG')).toBe('ft'); 1940 | expect(coder.roadHeightUnit('IM')).toBe('ft'); 1941 | expect(coder.roadHeightUnit('GB-ENG')).toBe('ft'); 1942 | expect(coder.roadHeightUnit('Great Britain')).toBe('ft'); 1943 | }); 1944 | 1945 | it('finds null for 001', () => { 1946 | expect(coder.roadHeightUnit('001')).toBeNull(); 1947 | }); 1948 | 1949 | it('finds null for location of North Pole', () => { 1950 | expect(coder.roadHeightUnit([0, 90])).toBeNull(); 1951 | }); 1952 | 1953 | it('finds null for United States due to variation in Puerto Rico', () => { 1954 | expect(coder.roadHeightUnit('US')).toBeNull(); 1955 | }); 1956 | }); 1957 | 1958 | describe('callingCodes', () => { 1959 | it('finds one prefix for feature with one', () => { 1960 | expect(coder.callingCodes([2.35, 48.85])).toStrictEqual(['33']); 1961 | expect(coder.callingCodes('ES-CE')).toStrictEqual(['34']); 1962 | }); 1963 | 1964 | it('finds multiple prefixes for feature with multiple', () => { 1965 | expect(coder.callingCodes('PR')).toStrictEqual(['1 787', '1 939']); 1966 | }); 1967 | 1968 | it('finds none for feature without data', () => { 1969 | expect(coder.callingCodes('Bir Tawil')).toStrictEqual([]); 1970 | }); 1971 | 1972 | it('finds none for location of North Pole', () => { 1973 | expect(coder.callingCodes([0, 90])).toStrictEqual([]); 1974 | }); 1975 | }); 1976 | }); 1977 | --------------------------------------------------------------------------------