@@ -11,64 +11,69 @@ var loggers = require('./loggers');
1111var isPlainObject = require ( './is_plain_object' ) ;
1212var nestedProperty = require ( './nested_property' ) ;
1313var polygon = require ( './polygon' ) ;
14+ const { usaLocationAbbreviations, usaLocationList } = require ( './usa_location_names' ) ;
1415
1516// make list of all country iso3 ids from at runtime
1617var countryIds = Object . keys ( countryRegex ) ;
1718
1819var locationmodeToIdFinder = {
1920 'ISO-3' : identity ,
20- 'USA-states' : identity ,
21+ 'USA-states' : usaLocationToAbbreviation ,
2122 'country names' : countryNameToISO3
2223} ;
2324
2425function countryNameToISO3 ( countryName ) {
25- for ( var i = 0 ; i < countryIds . length ; i ++ ) {
26+ for ( var i = 0 ; i < countryIds . length ; i ++ ) {
2627 var iso3 = countryIds [ i ] ;
2728 var regex = new RegExp ( countryRegex [ iso3 ] ) ;
2829
29- if ( regex . test ( countryName . trim ( ) . toLowerCase ( ) ) ) return iso3 ;
30+ if ( regex . test ( countryName . trim ( ) . toLowerCase ( ) ) ) return iso3 ;
3031 }
3132
3233 loggers . log ( 'Unrecognized country name: ' + countryName + '.' ) ;
3334
3435 return false ;
3536}
3637
37- function locationToFeature ( locationmode , location , features ) {
38- if ( ! location || typeof location !== 'string' ) return false ;
38+ function usaLocationToAbbreviation ( loc ) {
39+ loc = loc . trim ( ) ;
40+ const abbreviation = usaLocationAbbreviations . has ( loc . toUpperCase ( ) )
41+ ? loc . toUpperCase ( )
42+ : usaLocationList [ loc . toLowerCase ( ) ] ;
43+
44+ if ( abbreviation ) return abbreviation ;
45+
46+ loggers . log ( 'Unrecognized US location: ' + loc + '.' ) ;
47+
48+ return false ;
49+ }
3950
40- var locationId = locationmodeToIdFinder [ locationmode ] ( location ) ;
41- var filteredFeatures ;
42- var f , i ;
51+ function locationToFeature ( locationmode , location , features ) {
52+ if ( ! location || typeof location !== 'string' ) return false ;
4353
44- if ( locationId ) {
45- if ( locationmode === 'USA-states' ) {
54+ const locationId = locationmodeToIdFinder [ locationmode ] ( location ) ;
55+ if ( locationId ) {
56+ let filteredFeatures ;
57+ if ( locationmode === 'USA-states' ) {
4658 // Filter out features out in USA
4759 //
4860 // This is important as the Natural Earth files
4961 // include state/provinces from USA, Canada, Australia and Brazil
5062 // which have some overlay in their two-letter ids. For example,
5163 // 'WA' is used for both Washington state and Western Australia.
5264 filteredFeatures = [ ] ;
53- for ( i = 0 ; i < features . length ; i ++ ) {
54- f = features [ i ] ;
55- if ( f . properties && f . properties . gu && f . properties . gu === 'USA' ) {
56- filteredFeatures . push ( f ) ;
57- }
65+ for ( const f of features ) {
66+ if ( f ?. properties ?. gu === 'USA' ) filteredFeatures . push ( f ) ;
5867 }
5968 } else {
6069 filteredFeatures = features ;
6170 }
6271
63- for ( i = 0 ; i < filteredFeatures . length ; i ++ ) {
64- f = filteredFeatures [ i ] ;
65- if ( f . id === locationId ) return f ;
72+ for ( const f of filteredFeatures ) {
73+ if ( f . id === locationId ) return f ;
6674 }
6775
68- loggers . log ( [
69- 'Location with id' , locationId ,
70- 'does not have a matching topojson feature at this resolution.'
71- ] . join ( ' ' ) ) ;
76+ loggers . log ( `Location with id ${ locationId } does not have a matching topojson feature at this resolution.` ) ;
7277 }
7378
7479 return false ;
@@ -83,46 +88,43 @@ function feature2polygons(feature) {
8388 var appendPolygon , j , k , m ;
8489
8590 function doesCrossAntiMerdian ( pts ) {
86- for ( var l = 0 ; l < pts . length - 1 ; l ++ ) {
87- if ( pts [ l ] [ 0 ] > 0 && pts [ l + 1 ] [ 0 ] < 0 ) return l ;
91+ for ( var l = 0 ; l < pts . length - 1 ; l ++ ) {
92+ if ( pts [ l ] [ 0 ] > 0 && pts [ l + 1 ] [ 0 ] < 0 ) return l ;
8893 }
8994 return null ;
9095 }
9196
92- if ( loc === 'RUS' || loc === 'FJI' ) {
97+ if ( loc === 'RUS' || loc === 'FJI' ) {
9398 // Russia and Fiji have landmasses that cross the antimeridian,
9499 // we need to add +360 to their longitude coordinates, so that
95100 // polygon 'contains' doesn't get confused when crossing the antimeridian.
96101 //
97102 // Note that other countries have polygons on either side of the antimeridian
98103 // (e.g. some Aleutian island for the USA), but those don't confuse
99104 // the 'contains' method; these are skipped here.
100- appendPolygon = function ( _pts ) {
105+ appendPolygon = function ( _pts ) {
101106 var pts ;
102107
103- if ( doesCrossAntiMerdian ( _pts ) === null ) {
108+ if ( doesCrossAntiMerdian ( _pts ) === null ) {
104109 pts = _pts ;
105110 } else {
106111 pts = new Array ( _pts . length ) ;
107- for ( m = 0 ; m < _pts . length ; m ++ ) {
112+ for ( m = 0 ; m < _pts . length ; m ++ ) {
108113 // do not mutate calcdata[i][j].geojson !!
109- pts [ m ] = [
110- _pts [ m ] [ 0 ] < 0 ? _pts [ m ] [ 0 ] + 360 : _pts [ m ] [ 0 ] ,
111- _pts [ m ] [ 1 ]
112- ] ;
114+ pts [ m ] = [ _pts [ m ] [ 0 ] < 0 ? _pts [ m ] [ 0 ] + 360 : _pts [ m ] [ 0 ] , _pts [ m ] [ 1 ] ] ;
113115 }
114116 }
115117
116118 polygons . push ( polygon . tester ( pts ) ) ;
117119 } ;
118- } else if ( loc === 'ATA' ) {
120+ } else if ( loc === 'ATA' ) {
119121 // Antarctica has a landmass that wraps around every longitudes which
120122 // confuses the 'contains' methods.
121- appendPolygon = function ( pts ) {
123+ appendPolygon = function ( pts ) {
122124 var crossAntiMeridianIndex = doesCrossAntiMerdian ( pts ) ;
123125
124126 // polygon that do not cross anti-meridian need no special handling
125- if ( crossAntiMeridianIndex === null ) {
127+ if ( crossAntiMeridianIndex === null ) {
126128 return polygons . push ( polygon . tester ( pts ) ) ;
127129 }
128130
@@ -135,10 +137,10 @@ function feature2polygons(feature) {
135137 var stitch = new Array ( pts . length + 1 ) ;
136138 var si = 0 ;
137139
138- for ( m = 0 ; m < pts . length ; m ++ ) {
139- if ( m > crossAntiMeridianIndex ) {
140+ for ( m = 0 ; m < pts . length ; m ++ ) {
141+ if ( m > crossAntiMeridianIndex ) {
140142 stitch [ si ++ ] = [ pts [ m ] [ 0 ] + 360 , pts [ m ] [ 1 ] ] ;
141- } else if ( m === crossAntiMeridianIndex ) {
143+ } else if ( m === crossAntiMeridianIndex ) {
142144 stitch [ si ++ ] = pts [ m ] ;
143145 stitch [ si ++ ] = [ pts [ m ] [ 0 ] , - 90 ] ;
144146 } else {
@@ -155,21 +157,21 @@ function feature2polygons(feature) {
155157 } ;
156158 } else {
157159 // otherwise using same array ref is fine
158- appendPolygon = function ( pts ) {
160+ appendPolygon = function ( pts ) {
159161 polygons . push ( polygon . tester ( pts ) ) ;
160162 } ;
161163 }
162164
163- switch ( geometry . type ) {
165+ switch ( geometry . type ) {
164166 case 'MultiPolygon' :
165- for ( j = 0 ; j < coords . length ; j ++ ) {
166- for ( k = 0 ; k < coords [ j ] . length ; k ++ ) {
167+ for ( j = 0 ; j < coords . length ; j ++ ) {
168+ for ( k = 0 ; k < coords [ j ] . length ; k ++ ) {
167169 appendPolygon ( coords [ j ] [ k ] ) ;
168170 }
169171 }
170172 break ;
171173 case 'Polygon' :
172- for ( j = 0 ; j < coords . length ; j ++ ) {
174+ for ( j = 0 ; j < coords . length ; j ++ ) {
173175 appendPolygon ( coords [ j ] ) ;
174176 }
175177 break ;
@@ -185,7 +187,7 @@ function getTraceGeojson(trace) {
185187
186188 // This should not happen, but just in case something goes
187189 // really wrong when fetching the GeoJSON
188- if ( ! isPlainObject ( geojsonIn ) ) {
190+ if ( ! isPlainObject ( geojsonIn ) ) {
189191 loggers . error ( 'Oops ... something went wrong when fetching ' + g ) ;
190192 return false ;
191193 }
@@ -197,15 +199,15 @@ function extractTraceFeature(calcTrace) {
197199 var trace = calcTrace [ 0 ] . trace ;
198200
199201 var geojsonIn = getTraceGeojson ( trace ) ;
200- if ( ! geojsonIn ) return false ;
202+ if ( ! geojsonIn ) return false ;
201203
202204 var lookup = { } ;
203205 var featuresOut = [ ] ;
204206 var i ;
205207
206- for ( i = 0 ; i < trace . _length ; i ++ ) {
208+ for ( i = 0 ; i < trace . _length ; i ++ ) {
207209 var cdi = calcTrace [ i ] ;
208- if ( cdi . loc || cdi . loc === 0 ) {
210+ if ( cdi . loc || cdi . loc === 0 ) {
209211 lookup [ cdi . loc ] = cdi ;
210212 }
211213 }
@@ -214,10 +216,10 @@ function extractTraceFeature(calcTrace) {
214216 var id = nestedProperty ( fIn , trace . featureidkey || 'id' ) . get ( ) ;
215217 var cdi = lookup [ id ] ;
216218
217- if ( cdi ) {
219+ if ( cdi ) {
218220 var geometry = fIn . geometry ;
219221
220- if ( geometry . type === 'Polygon' || geometry . type === 'MultiPolygon' ) {
222+ if ( geometry . type === 'Polygon' || geometry . type === 'MultiPolygon' ) {
221223 var fOut = {
222224 type : 'Feature' ,
223225 id : id ,
@@ -238,11 +240,15 @@ function extractTraceFeature(calcTrace) {
238240
239241 featuresOut . push ( fOut ) ;
240242 } else {
241- loggers . log ( [
242- 'Location' , cdi . loc , 'does not have a valid GeoJSON geometry.' ,
243- 'Traces with locationmode *geojson-id* only support' ,
244- '*Polygon* and *MultiPolygon* geometries.'
245- ] . join ( ' ' ) ) ;
243+ loggers . log (
244+ [
245+ 'Location' ,
246+ cdi . loc ,
247+ 'does not have a valid GeoJSON geometry.' ,
248+ 'Traces with locationmode *geojson-id* only support' ,
249+ '*Polygon* and *MultiPolygon* geometries.'
250+ ] . join ( ' ' )
251+ ) ;
246252 }
247253 }
248254
@@ -251,31 +257,36 @@ function extractTraceFeature(calcTrace) {
251257 delete lookup [ id ] ;
252258 }
253259
254- switch ( geojsonIn . type ) {
260+ switch ( geojsonIn . type ) {
255261 case 'FeatureCollection' :
256262 var featuresIn = geojsonIn . features ;
257- for ( i = 0 ; i < featuresIn . length ; i ++ ) {
263+ for ( i = 0 ; i < featuresIn . length ; i ++ ) {
258264 appendFeature ( featuresIn [ i ] ) ;
259265 }
260266 break ;
261267 case 'Feature' :
262268 appendFeature ( geojsonIn ) ;
263269 break ;
264270 default :
265- loggers . warn ( [
266- 'Invalid GeoJSON type' , ( geojsonIn . type || 'none' ) + '.' ,
267- 'Traces with locationmode *geojson-id* only support' ,
268- '*FeatureCollection* and *Feature* types.'
269- ] . join ( ' ' ) ) ;
271+ loggers . warn (
272+ [
273+ 'Invalid GeoJSON type' ,
274+ ( geojsonIn . type || 'none' ) + '.' ,
275+ 'Traces with locationmode *geojson-id* only support' ,
276+ '*FeatureCollection* and *Feature* types.'
277+ ] . join ( ' ' )
278+ ) ;
270279 return false ;
271280 }
272281
273- for ( var loc in lookup ) {
274- loggers . log ( [
275- 'Location *' + loc + '*' ,
276- 'does not have a matching feature with id-key' ,
277- '*' + trace . featureidkey + '*.'
278- ] . join ( ' ' ) ) ;
282+ for ( var loc in lookup ) {
283+ loggers . log (
284+ [
285+ 'Location *' + loc + '*' ,
286+ 'does not have a matching feature with id-key' ,
287+ '*' + trace . featureidkey + '*.'
288+ ] . join ( ' ' )
289+ ) ;
279290 }
280291
281292 return featuresOut ;
@@ -289,14 +300,14 @@ function findCentroid(feature) {
289300 var geometry = feature . geometry ;
290301 var poly ;
291302
292- if ( geometry . type === 'MultiPolygon' ) {
303+ if ( geometry . type === 'MultiPolygon' ) {
293304 var coords = geometry . coordinates ;
294305 var maxArea = 0 ;
295306
296- for ( var i = 0 ; i < coords . length ; i ++ ) {
297- var polyi = { type : 'Polygon' , coordinates : coords [ i ] } ;
307+ for ( var i = 0 ; i < coords . length ; i ++ ) {
308+ var polyi = { type : 'Polygon' , coordinates : coords [ i ] } ;
298309 var area = turfArea ( polyi ) ;
299- if ( area > maxArea ) {
310+ if ( area > maxArea ) {
300311 maxArea = area ;
301312 poly = polyi ;
302313 }
@@ -313,13 +324,14 @@ function fetchTraceGeoData(calcData) {
313324 var promises = [ ] ;
314325
315326 function fetch ( url ) {
316- return new Promise ( function ( resolve , reject ) {
317- d3 . json ( url , function ( err , d ) {
318- if ( err ) {
327+ return new Promise ( function ( resolve , reject ) {
328+ d3 . json ( url , function ( err , d ) {
329+ if ( err ) {
319330 delete PlotlyGeoAssets [ url ] ;
320- var msg = err . status === 404 ?
321- ( 'GeoJSON at URL "' + url + '" does not exist.' ) :
322- ( 'Unexpected error while fetching from ' + url ) ;
331+ var msg =
332+ err . status === 404
333+ ? 'GeoJSON at URL "' + url + '" does not exist.'
334+ : 'Unexpected error while fetching from ' + url ;
323335 return reject ( new Error ( msg ) ) ;
324336 }
325337
@@ -330,14 +342,14 @@ function fetchTraceGeoData(calcData) {
330342 }
331343
332344 function wait ( url ) {
333- return new Promise ( function ( resolve , reject ) {
345+ return new Promise ( function ( resolve , reject ) {
334346 var cnt = 0 ;
335- var interval = setInterval ( function ( ) {
336- if ( PlotlyGeoAssets [ url ] && PlotlyGeoAssets [ url ] !== 'pending' ) {
347+ var interval = setInterval ( function ( ) {
348+ if ( PlotlyGeoAssets [ url ] && PlotlyGeoAssets [ url ] !== 'pending' ) {
337349 clearInterval ( interval ) ;
338350 return resolve ( PlotlyGeoAssets [ url ] ) ;
339351 }
340- if ( cnt > 100 ) {
352+ if ( cnt > 100 ) {
341353 clearInterval ( interval ) ;
342354 return reject ( 'Unexpected error while fetching from ' + url ) ;
343355 }
@@ -346,15 +358,15 @@ function fetchTraceGeoData(calcData) {
346358 } ) ;
347359 }
348360
349- for ( var i = 0 ; i < calcData . length ; i ++ ) {
361+ for ( var i = 0 ; i < calcData . length ; i ++ ) {
350362 var trace = calcData [ i ] [ 0 ] . trace ;
351363 var url = trace . geojson ;
352364
353- if ( typeof url === 'string' ) {
354- if ( ! PlotlyGeoAssets [ url ] ) {
365+ if ( typeof url === 'string' ) {
366+ if ( ! PlotlyGeoAssets [ url ] ) {
355367 PlotlyGeoAssets [ url ] = 'pending' ;
356368 promises . push ( fetch ( url ) ) ;
357- } else if ( PlotlyGeoAssets [ url ] === 'pending' ) {
369+ } else if ( PlotlyGeoAssets [ url ] === 'pending' ) {
358370 promises . push ( wait ( url ) ) ;
359371 }
360372 }
0 commit comments