Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 1751931
Showing
13 changed files
with
803 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
.DS_Store | ||
.esm-cache | ||
.vscode | ||
.coverage | ||
|
||
node_modules/ | ||
built/ | ||
|
||
npm-debug.log | ||
package-lock.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
## ISC License | ||
|
||
Copyright (c) iD Contributors | ||
|
||
Permission to use, copy, modify, and/or distribute this software for any | ||
purpose with or without fee is hereby granted, provided that the above | ||
copyright notice and this permission notice appear in all copies. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH | ||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY | ||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, | ||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM | ||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR | ||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR | ||
PERFORMANCE OF THIS SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module.exports = require('babel-jest').createTransformer({ | ||
rootMode: 'upward' | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module.exports = { | ||
presets: [['@babel/preset-env', { targets: { node: 'current' } }]] | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
module.exports = { | ||
collectCoverage: true, | ||
collectCoverageFrom: ['**/built/cjs/*.js', '!**/node_modules/**'], | ||
coverageDirectory: '<rootDir>/.coverage', | ||
transform: { | ||
'^.+\\.jsx?$': '<rootDir>/babel-jest-wrapper.js' | ||
}, | ||
setupFilesAfterEnv: ['jest-extended'], | ||
verbose: true | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
{ | ||
"name": "country-coder", | ||
"version": "1.0.0-pre.0", | ||
"description": "Convert longitude-latitude pairs to ISO 3166-1 codes quickly and locally", | ||
"repository": "ideditor/country-coder", | ||
"main": "built/cjs/country-coder.js", | ||
"module": "built/es6/country-coder.js", | ||
"license": "ISC", | ||
"contributors": [ | ||
"Quincy Morgan <@quincylvania>" | ||
], | ||
"keywords": [ | ||
"reverse geocoder", | ||
"country codes", | ||
"ISO 3166-1", | ||
"geolocation" | ||
], | ||
"devDependencies": { | ||
"@babel/core": "^7.5.4", | ||
"@babel/preset-env": "^7.5.4", | ||
"@types/node": "^12.6.0", | ||
"babel-jest": "^24.8.0", | ||
"jest": "^24.8.0", | ||
"jest-extended": "^0.11.2", | ||
"npm-run-all": "^4.0.0", | ||
"prettier": "^1.18.2", | ||
"shx": "^0.3.0", | ||
"typescript": "^3.5.2" | ||
}, | ||
"dependencies": { | ||
"which-polygon": "^2.2.0" | ||
}, | ||
"scripts": { | ||
"all": "npm-run-all -s clean build pretty", | ||
"clean": "shx rm -rf ./built/*", | ||
"build": "npm-run-all -s build:**", | ||
"build:es6": "tsc --project ./tsconfig.es6.json", | ||
"build:cjs": "tsc --project ./tsconfig.cjs.json", | ||
"test": "jest --config jest.config.js tests/*.js", | ||
"pretty": "prettier --write \"./**/*.ts\"" | ||
}, | ||
"engines": { | ||
"node": ">=8.15.0", | ||
"npm": ">=5.0.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
module.exports = { | ||
arrowParens: 'always', | ||
printWidth: 100, | ||
singleQuote: true, | ||
trailingComma: 'none' | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
import whichPolygon from 'which-polygon'; | ||
import * as geojson from './data/borders.json'; | ||
|
||
type FeatureProperties = { | ||
// ISO 3166-1 alpha-2 code | ||
iso1A2: string; | ||
// ISO 3166-1 alpha-3 code | ||
iso1A3: string | undefined; | ||
// ISO 3166-1 numeric-3 code | ||
iso1N3: string | undefined; | ||
// Wikidata QID | ||
wikidata: string | undefined; | ||
// for features entirely within a country, the ISO 3166-1 alpha-2 code for that country | ||
country: string | undefined; | ||
// the ISO 3166-1 alpha-2 codes of other features this feature is entirely within, other than its country | ||
groups: Array<string> | undefined; | ||
// additional differentiator for some features which aren't countries | ||
// - `intGroup`: an international organization | ||
type: string | undefined; | ||
// the status of this feature's ISO 3166-1 code(s) if they are not officially-assigned | ||
// - `excRes`: exceptionally-reserved | ||
// - `usrAssn`: user-assigned | ||
isoStatus: string | undefined; | ||
}; | ||
type Feature = { type: string; geometry: any; properties: FeatureProperties }; | ||
type FeatureCollection = { type: string; features: Array<Feature> }; | ||
type Vec2 = [number, number]; // [lon, lat] | ||
|
||
export default class CountryCoder { | ||
private featureQuery: any = {}; | ||
private featuresByCode: any = {}; | ||
|
||
// Constructs a new CountryCoder | ||
constructor() { | ||
let borders: FeatureCollection = (<any>geojson).default; | ||
let geometryFeatures: Array<Feature> = []; | ||
for (let i in borders.features) { | ||
let feature = borders.features[i]; | ||
this.featuresByCode[feature.properties.iso1A2] = feature; | ||
if (feature.geometry) { | ||
geometryFeatures.push(feature); | ||
} | ||
} | ||
// whichPolygon doesn't support null geometry even though GeoJSON does | ||
let geometryOnlyCollection: FeatureCollection = { | ||
type: 'FeatureCollection', | ||
features: geometryFeatures | ||
}; | ||
this.featureQuery = whichPolygon(geometryOnlyCollection); | ||
} | ||
|
||
// Returns the smallest feature of any code status containing `loc`, if any | ||
smallestFeature(loc: Vec2): Feature | null { | ||
let featureProperties: FeatureProperties = this.featureQuery(loc); | ||
if (!featureProperties) return null; | ||
return this.featuresByCode[featureProperties.iso1A2]; | ||
} | ||
|
||
// Returns all the features containing `loc`, if any | ||
features(loc: Vec2): Array<Feature> { | ||
let feature = this.smallestFeature(loc); | ||
if (!feature) return []; | ||
|
||
let features: Array<Feature> = []; | ||
let featuresByCode = this.featuresByCode; | ||
function addAndCheckCode(code) { | ||
let codeFeature = featuresByCode[code]; | ||
if (features.indexOf(codeFeature) !== -1) return; | ||
features.push(codeFeature); | ||
let properties = codeFeature.properties; | ||
if (properties.groups) { | ||
for (let i in properties.groups) { | ||
addAndCheckCode(properties.groups[i]); | ||
} | ||
} | ||
if (properties.country) { | ||
addAndCheckCode(properties.country); | ||
} | ||
} | ||
addAndCheckCode(feature.properties.iso1A2); | ||
return features; | ||
} | ||
|
||
// Returns the ISO 3166-1 alpha-2 code for the country containing `loc`, if any | ||
// e.g. a location in Puerto Rico will return US | ||
countryIso1A2Code(loc: Vec2): string | null { | ||
let feature = this.smallestFeature(loc); | ||
if (!feature) return null; | ||
// `country` can be explicit; | ||
// a feature without `country` but with geometry is itself a country | ||
return feature.properties.country || feature.properties.iso1A2; | ||
} | ||
|
||
// Returns the country feature containing `loc`, if any | ||
countryFeature(loc: Vec2): Feature | null { | ||
let countryCode = this.countryIso1A2Code(loc); | ||
if (!countryCode) return null; | ||
return this.featuresByCode[countryCode]; | ||
} | ||
|
||
// Returns the ISO 3166-1 alpha-3 code for the country containing `loc`, if any | ||
// e.g. a location in Puerto Rico will return USA | ||
countryIso1A3Code(loc: Vec2): string | null { | ||
let feature = this.countryFeature(loc); | ||
if (!feature) return null; | ||
return feature.properties.iso1A3 || null; | ||
} | ||
|
||
// Returns the ISO 3166-1 numeric-3 code for the country containing `loc`, if any | ||
countryIso1N3Code(loc: Vec2): string | null { | ||
let feature = this.countryFeature(loc); | ||
if (!feature) return null; | ||
return feature.properties.iso1N3 || null; | ||
} | ||
|
||
// Returns the Wikidata QID code for the country containing `loc`, if any | ||
countryWikidataQID(loc: Vec2): string | null { | ||
let feature = this.countryFeature(loc); | ||
if (!feature) return null; | ||
return feature.properties.wikidata || null; | ||
} | ||
|
||
// Returns the smallest feature containing `loc` to have an officially-assigned code, if any | ||
smallestOfficialIsoFeature(loc: Vec2): Feature | null { | ||
return ( | ||
this.features(loc).find(function(feature) { | ||
return !feature.properties.isoStatus; // features without an explicit status are officially-assigned | ||
}) || null | ||
); | ||
} | ||
|
||
// Returns the ISO 3166-1 alpha-2 code of the smallest feature containing `loc` to have an officially-assigned code, if any | ||
// e.g. a location in Puerto Rico will return PR | ||
officialIso1A2Code(loc: Vec2): string | null { | ||
let feature = this.smallestOfficialIsoFeature(loc); | ||
if (!feature) return null; | ||
return feature.properties.iso1A2; | ||
} | ||
|
||
// Returns the ISO 3166-1 alpha-2 codes for all features containing `loc`, if any | ||
iso1A2Codes(loc: Vec2): Array<string> { | ||
return this.features(loc).map(function(feature) { | ||
return feature.properties.iso1A2; | ||
}); | ||
} | ||
|
||
// Returns true if `loc` is in an EU member state | ||
isInEuropeanUnion(loc: Vec2): boolean { | ||
return this.iso1A2Codes(loc).indexOf('EU') !== -1; | ||
} | ||
} |
Oops, something went wrong.