import whichPolygon from 'which-polygon' ; import rawBorders from './data/borders.json' ; type RegionFeatureProperties = { id : string ; iso1A2 : string | undefined ; iso1A3 : string | undefined ; iso1N3 : string | undefined ; m49 : string | undefined ; wikidata : string ; emojiFlag : string | undefined ; ccTLD : string | undefined ; nameEn : string ; aliases : Array < string > | undefined ; country : string | undefined ; groups : Array < string > ; members : Array < string > | undefined ; level : string ; isoStatus : string | undefined ; driveSide : string | undefined ; roadSpeedUnit : string | undefined ; roadHeightUnit : string | undefined ; callingCodes : Array < string > | undefined ; } ; type RegionFeature = { type : string ; geometry : any ; properties : RegionFeatureProperties ; } ; type RegionFeatureCollection = { type : string ; features : Array < RegionFeature > ; } ; type Vec2 = [ number , number ] ; type Bbox = [ number , number , number , number ] ; type PointGeometry = { type : string ; coordinates : Vec2 ; } ; type PointFeature = { type : string ; geometry : PointGeometry ; properties : any ; } ; type Location = Vec2 | PointGeometry | PointFeature ; type CodingOptions = { level ? : string | undefined ; maxLevel ? : string | undefined ; withProp ? : string | undefined ; } ; export let borders = < RegionFeatureCollection > rawBorders ; let whichPolygonGetter = { } ; let featuresByCode = { } ; let idFilterRegex = /(?=(?!^(and|the|of|el|la|de)$))(\b(and|the|of|el|la|de)\b)|[-_ .,'()&[\]/]/gi ; function canonicalID ( id ) { let s = id || '' ; if ( s . charAt ( 0 ) === '.' ) { return s . toUpperCase ( ) ; } else { return s . replace ( idFilterRegex , '' ) . toUpperCase ( ) ; } } let levels = [ 'subterritory' , 'territory' , 'subcountryGroup' , 'country' , 'sharedLandform' , 'intermediateRegion' , 'subregion' , 'region' , 'subunion' , 'union' , 'unitedNations' , 'world' ] ; loadDerivedDataAndCaches ( borders ) ; function loadDerivedDataAndCaches ( borders ) { let identifierProps = [ 'iso1A2' , 'iso1A3' , 'm49' , 'wikidata' , 'emojiFlag' , 'ccTLD' , 'nameEn' ] ; let geometryFeatures = [ ] ; for ( let i in borders . features ) { let feature = borders . features [ i ] ; feature . properties . id = feature . properties . iso1A2 || feature . properties . m49 || feature . properties . wikidata ; loadM49 ( feature ) ; loadTLD ( feature ) ; loadIsoStatus ( feature ) ; loadLevel ( feature ) ; loadGroups ( feature ) ; loadFlag ( feature ) ; cacheFeatureByIDs ( feature ) ; if ( feature . geometry ) geometryFeatures . push ( feature ) ; } for ( let i in borders . features ) { let feature = borders . features [ i ] ; feature . properties . groups = feature . properties . groups . map ( function ( groupID ) { return featuresByCode [ groupID ] . properties . id ; } ) ; loadMembersForGroupsOf ( feature ) ; } for ( let i in borders . features ) { let feature = borders . features [ i ] ; loadRoadSpeedUnit ( feature ) ; loadRoadHeightUnit ( feature ) ; loadDriveSide ( feature ) ; loadCallingCodes ( feature ) ; loadGroupGroups ( feature ) ; } for ( let i in borders . features ) { let feature = borders . features [ i ] ; feature . properties . groups . sort ( function ( groupID1 , groupID2 ) { return ( levels . indexOf ( featuresByCode [ groupID1 ] . properties . level ) - levels . indexOf ( featuresByCode [ groupID2 ] . properties . level ) ) ; } ) ; if ( feature . properties . members ) feature . properties . members . sort ( function ( id1 , id2 ) { let diff = levels . indexOf ( featuresByCode [ id1 ] . properties . level ) - levels . indexOf ( featuresByCode [ id2 ] . properties . level ) ; if ( diff === 0 ) { return ( borders . features . indexOf ( featuresByCode [ id1 ] ) - borders . features . indexOf ( featuresByCode [ id2 ] ) ) ; } return diff ; } ) ; } let geometryOnlyCollection = { type : 'FeatureCollection' , features : geometryFeatures } ; whichPolygonGetter = whichPolygon ( geometryOnlyCollection ) ; function loadGroups ( feature ) { let props = feature . properties ; if ( ! props . groups ) { props . groups = [ ] ; } if ( feature . geometry && props . country ) { props . groups . push ( props . country ) ; } if ( props . m49 !== '001' ) { props . groups . push ( '001' ) ; } } function loadM49 ( feature ) { let props = feature . properties ; if ( ! props . m49 && props . iso1N3 ) { props . m49 = props . iso1N3 ; } } function loadTLD ( feature ) { let props = feature . properties ; if ( props . level === 'unitedNations' ) return ; if ( ! props . ccTLD && props . iso1A2 ) { props . ccTLD = '.' + props . iso1A2 . toLowerCase ( ) ; } } function loadIsoStatus ( feature ) { let props = feature . properties ; if ( ! props . isoStatus && props . iso1A2 ) { props . isoStatus = 'official' ; } } function loadLevel ( feature ) { let props = feature . properties ; if ( props . level ) return ; if ( ! props . country ) { props . level = 'country' ; } else if ( ! props . iso1A2 || props . isoStatus === 'official' ) { props . level = 'territory' ; } else { props . level = 'subterritory' ; } } function loadGroupGroups ( feature ) { let props = feature . properties ; if ( feature . geometry || ! props . members ) return ; let featureLevelIndex = levels . indexOf ( props . level ) ; let sharedGroups = [ ] ; for ( let i in props . members ) { let memberID = props . members [ i ] ; let member = featuresByCode [ memberID ] ; let memberGroups = member . properties . groups . filter ( function ( groupID ) { return ( groupID !== feature . properties . id && featureLevelIndex < levels . indexOf ( featuresByCode [ groupID ] . properties . level ) ) ; } ) ; if ( i === '0' ) { sharedGroups = memberGroups ; } else { sharedGroups = sharedGroups . filter ( function ( groupID ) { return memberGroups . indexOf ( groupID ) !== - 1 ; } ) ; } } props . groups = props . groups . concat ( sharedGroups . filter ( function ( groupID ) { return props . groups . indexOf ( groupID ) === - 1 ; } ) ) ; for ( let j in sharedGroups ) { let groupFeature = featuresByCode [ sharedGroups [ j ] ] ; if ( groupFeature . properties . members . indexOf ( props . id ) === - 1 ) { groupFeature . properties . members . push ( props . id ) ; } } } function loadRoadSpeedUnit ( feature ) { let props = feature . properties ; if ( feature . geometry ) { if ( ! props . roadSpeedUnit ) props . roadSpeedUnit = 'km/h' ; } else if ( props . members ) { let vals = Array . from ( new Set ( props . members . map ( function ( id ) { let member = featuresByCode [ id ] ; if ( member . geometry ) return member . properties . roadSpeedUnit || 'km/h' ; } ) . filter ( Boolean ) ) ) ; if ( vals . length === 1 ) props . roadSpeedUnit = vals [ 0 ] ; } } function loadRoadHeightUnit ( feature ) { let props = feature . properties ; if ( feature . geometry ) { if ( ! props . roadHeightUnit ) props . roadHeightUnit = 'm' ; } else if ( props . members ) { let vals = Array . from ( new Set ( props . members . map ( function ( id ) { let member = featuresByCode [ id ] ; if ( member . geometry ) return member . properties . roadHeightUnit || 'm' ; } ) . filter ( Boolean ) ) ) ; if ( vals . length === 1 ) props . roadHeightUnit = vals [ 0 ] ; } } function loadDriveSide ( feature ) { let props = feature . properties ; if ( feature . geometry ) { if ( ! props . driveSide ) props . driveSide = 'right' ; } else if ( props . members ) { let vals = Array . from ( new Set ( props . members . map ( function ( id ) { let member = featuresByCode [ id ] ; if ( member . geometry ) return member . properties . driveSide || 'right' ; } ) . filter ( Boolean ) ) ) ; if ( vals . length === 1 ) props . driveSide = vals [ 0 ] ; } } function loadCallingCodes ( feature ) { let props = feature . properties ; if ( ! feature . geometry && props . members ) { props . callingCodes = Array . from ( new Set ( props . members . reduce ( function ( array , id ) { let member = featuresByCode [ id ] ; if ( member . geometry && member . properties . callingCodes ) return array . concat ( member . properties . callingCodes ) ; return array ; } , [ ] ) ) ) ; } } function loadFlag ( feature ) { if ( ! feature . properties . iso1A2 ) return ; let flag = feature . properties . iso1A2 . replace ( /./g , function ( char ) { return String . fromCodePoint ( < number > char . charCodeAt ( 0 ) + 127397 ) ; } ) ; feature . properties . emojiFlag = flag ; } function loadMembersForGroupsOf ( feature ) { for ( let j in feature . properties . groups ) { let groupID = feature . properties . groups [ j ] ; let groupFeature = featuresByCode [ groupID ] ; if ( ! groupFeature . properties . members ) groupFeature . properties . members = [ ] ; groupFeature . properties . members . push ( feature . properties . id ) ; } } function cacheFeatureByIDs ( feature ) { let ids = [ ] ; for ( let k in identifierProps ) { let prop = identifierProps [ k ] ; let id = feature . properties [ prop ] ; if ( id ) ids . push ( id ) ; } if ( feature . properties . aliases ) { for ( let j in feature . properties . aliases ) { ids . push ( feature . properties . aliases [ j ] ) ; } } for ( let i in ids ) { let id = canonicalID ( ids [ i ] ) ; featuresByCode [ id ] = feature ; } } } function locArray ( loc ) { if ( Array . isArray ( loc ) ) { return < Vec2 > loc ; } else if ( ( < PointGeometry > loc ) . coordinates ) { return ( < PointGeometry > loc ) . coordinates ; } return ( < PointFeature > loc ) . geometry . coordinates ; } function smallestFeature ( loc ) { let query = locArray ( loc ) ; let featureProperties = whichPolygonGetter ( query ) ; if ( ! featureProperties ) return null ; return featuresByCode [ featureProperties . id ] ; } function countryFeature ( loc ) { let feature = smallestFeature ( loc ) ; if ( ! feature ) return null ; let countryCode = feature . properties . country || feature . properties . iso1A2 ; return featuresByCode [ < string > countryCode ] || null ; } let defaultOpts = { level : undefined , maxLevel : undefined , withProp : undefined } ; function featureForLoc ( loc , opts ) { let targetLevel = opts . level || 'country' ; let maxLevel = opts . maxLevel || 'world' ; let withProp = opts . withProp ; let targetLevelIndex = levels . indexOf ( targetLevel ) ; if ( targetLevelIndex === - 1 ) return null ; let maxLevelIndex = levels . indexOf ( maxLevel ) ; if ( maxLevelIndex === - 1 ) return null ; if ( maxLevelIndex < targetLevelIndex ) return null ; if ( targetLevel === 'country' ) { let fastFeature = countryFeature ( loc ) ; if ( fastFeature ) { if ( ! withProp || fastFeature . properties [ withProp ] ) { return fastFeature ; } } } let features = featuresContaining ( loc ) ; for ( let i in features ) { let feature = features [ i ] ; let levelIndex = levels . indexOf ( feature . properties . level ) ; if ( feature . properties . level === targetLevel || ( levelIndex > targetLevelIndex && levelIndex <= maxLevelIndex ) ) { if ( ! withProp || feature . properties [ withProp ] ) { return feature ; } } } return null ; } function featureForID ( id ) { let stringID ; if ( typeof id === 'number' ) { stringID = id . toString ( ) ; if ( stringID . length === 1 ) { stringID = '00' + stringID ; } else if ( stringID . length === 2 ) { stringID = '0' + stringID ; } } else { stringID = canonicalID ( id ) ; } return featuresByCode [ stringID ] || null ; } function smallestFeaturesForBbox ( bbox ) { return whichPolygonGetter . bbox ( bbox ) . map ( function ( props ) { return featuresByCode [ props . id ] ; } ) ; } function smallestOrMatchingFeature ( query ) { if ( typeof query === 'object' ) { return smallestFeature ( < Location > query ) ; } return featureForID ( query ) ; } export function feature ( query , opts = defaultOpts ) { if ( typeof query === 'object' ) { return featureForLoc ( < Location > query , opts ) ; } return featureForID ( query ) ; } export function iso1A2Code ( query , opts = defaultOpts ) { opts . withProp = 'iso1A2' ; let match = feature ( query , opts ) ; if ( ! match ) return null ; return match . properties . iso1A2 || null ; } export function iso1A3Code ( query , opts = defaultOpts ) { opts . withProp = 'iso1A3' ; let match = feature ( query , opts ) ; if ( ! match ) return null ; return match . properties . iso1A3 || null ; } export function iso1N3Code ( query , opts = defaultOpts ) { opts . withProp = 'iso1N3' ; let match = feature ( query , opts ) ; if ( ! match ) return null ; return match . properties . iso1N3 || null ; } export function m49Code ( query , opts = defaultOpts ) { opts . withProp = 'm49' ; let match = feature ( query , opts ) ; if ( ! match ) return null ; return match . properties . m49 || null ; } export function wikidataQID ( query , opts = defaultOpts ) { opts . withProp = 'wikidata' ; let match = feature ( query , opts ) ; if ( ! match ) return null ; return match . properties . wikidata ; } export function emojiFlag ( query , opts = defaultOpts ) { opts . withProp = 'emojiFlag' ; let match = feature ( query , opts ) ; if ( ! match ) return null ; return match . properties . emojiFlag || null ; } export function ccTLD ( query , opts = defaultOpts ) { opts . withProp = 'ccTLD' ; let match = feature ( query , opts ) ; if ( ! match ) return null ; return match . properties . ccTLD || null ; } function propertiesForQuery ( query , property ) { let features = featuresContaining ( query , false ) ; return features . map ( function ( feature ) { return feature . properties [ property ] ; } ) . filter ( Boolean ) ; } export function iso1A2Codes ( query ) { return propertiesForQuery ( query , 'iso1A2' ) ; } export function iso1A3Codes ( query ) { return propertiesForQuery ( query , 'iso1A3' ) ; } export function iso1N3Codes ( query ) { return propertiesForQuery ( query , 'iso1N3' ) ; } export function m49Codes ( query ) { return propertiesForQuery ( query , 'm49' ) ; } export function wikidataQIDs ( query ) { return propertiesForQuery ( query , 'wikidata' ) ; } export function emojiFlags ( query ) { return propertiesForQuery ( query , 'emojiFlag' ) ; } export function ccTLDs ( query ) { return propertiesForQuery ( query , 'ccTLD' ) ; } export function featuresContaining ( query , strict ? ) { let matchingFeatures ; if ( Array . isArray ( query ) && query . length === 4 ) { matchingFeatures = smallestFeaturesForBbox ( < Bbox > query ) ; } else { let smallestOrMatching = smallestOrMatchingFeature ( < Location | string | number > query ) ; matchingFeatures = smallestOrMatching ? [ smallestOrMatching ] : [ ] ; } if ( ! matchingFeatures . length ) return [ ] ; let returnFeatures ; if ( ! strict || typeof query === 'object' ) { returnFeatures = matchingFeatures . slice ( ) ; } else { returnFeatures = [ ] ; } for ( let j in matchingFeatures ) { let properties = matchingFeatures [ j ] . properties ; for ( let i in properties . groups ) { let groupID = properties . groups [ i ] ; let groupFeature = featuresByCode [ groupID ] ; if ( returnFeatures . indexOf ( groupFeature ) === - 1 ) { returnFeatures . push ( groupFeature ) ; } } } return returnFeatures ; } export function featuresIn ( id , strict ? ) { let feature = featureForID ( id ) ; if ( ! feature ) return [ ] ; let features = [ ] ; if ( ! strict ) { features . push ( feature ) ; } let properties = feature . properties ; if ( properties . members ) { for ( let i in properties . members ) { let memberID = properties . members [ i ] ; features . push ( featuresByCode [ memberID ] ) ; } } return features ; } export function aggregateFeature ( id ) { let features = featuresIn ( id , false ) ; if ( features . length === 0 ) return null ; let aggregateCoordinates = [ ] ; for ( let i in features ) { let feature = features [ i ] ; if ( feature . geometry && feature . geometry . type === 'MultiPolygon' && feature . geometry . coordinates ) { aggregateCoordinates = aggregateCoordinates . concat ( feature . geometry . coordinates ) ; } } return { type : 'Feature' , properties : features [ 0 ] . properties , geometry : { type : 'MultiPolygon' , coordinates : aggregateCoordinates } } ; } export function isIn ( query , bounds ) { let queryFeature = smallestOrMatchingFeature ( query ) ; let boundsFeature = featureForID ( bounds ) ; if ( ! queryFeature || ! boundsFeature ) return null ; if ( queryFeature . properties . id === boundsFeature . properties . id ) return true ; return queryFeature . properties . groups . indexOf ( boundsFeature . properties . id ) !== - 1 ; } export function isInEuropeanUnion ( query ) { return isIn ( query , 'EU' ) ; } export function isInUnitedNations ( query ) { return isIn ( query , 'UN' ) ; } export function driveSide ( query ) { let feature = smallestOrMatchingFeature ( query ) ; return ( feature && feature . properties . driveSide ) || null ; } export function roadSpeedUnit ( query ) { let feature = smallestOrMatchingFeature ( query ) ; return ( feature && feature . properties . roadSpeedUnit ) || null ; } export function roadHeightUnit ( query ) { let feature = smallestOrMatchingFeature ( query ) ; return ( feature && feature . properties . roadHeightUnit ) || null ; } export function callingCodes ( query ) { let feature = smallestOrMatchingFeature ( query ) ; return ( feature && feature . properties . callingCodes ) || [ ] ; }