@dra2020/district-analytics 10.0.2 → 10.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -230,58 +230,6 @@ class AnalyticsSession {
230
230
  // 1. All polygons in a multi-polygon; and
231
231
  // 2. All holes in a otherwise cohesive polygon.
232
232
  // Note that all non-cohesive features are always simple polygons.
233
- /*
234
- let i: number, j: number;
235
- let nPoly: number = 0;
236
- for (i = 0;nPoly == 0 && i < this.cacheDistricts.features.length;i++)
237
- {
238
- let f = this.cacheDistricts.features[i];
239
-
240
- if (f.geometry.type === 'MultiPolygon')
241
- nPoly += f.geometry.coordinates.length;
242
- else if (f.geometry.type === 'Polygon' && f.geometry.coordinates.length)
243
- nPoly += (f.geometry.coordinates.length - 1);
244
- }
245
- if (nPoly)
246
- {
247
- this.cacheNoncohesive = {type: 'FeatureCollection', features: []};
248
- let af: any = this.cacheNoncohesive.features;
249
- let oUnique: any = {};
250
-
251
- // First add discontiguous polygons
252
- for (i = 0;i < this.cacheDistricts.features.length;i++)
253
- {
254
- let f = this.cacheDistricts.features[i];
255
-
256
- if (f.geometry.type === 'MultiPolygon')
257
- {
258
- // Push all non-contiguous polygons
259
- for (j = 0;j < f.geometry.coordinates.length;j++)
260
- {
261
- let p: any = f.geometry.coordinates[j];
262
- oUnique[Hash.qhash(p[0])] = true;
263
- af.push({type: 'Feature', properties: {id: `${af.length + 1}`}, geometry: {type: 'Polygon', coordinates: p}});
264
- }
265
- }
266
- }
267
-
268
- // Now add unique holes
269
- for (i = 0;i < this.cacheDistricts.features.length;i++)
270
- {
271
- let f = this.cacheDistricts.features[i];
272
-
273
- if (f.geometry.type === 'Polygon')
274
- {
275
- // Push all holes from this polygon
276
- for (j = 1;j < f.geometry.coordinates.length;j++)
277
- {
278
- let p: any = f.geometry.coordinates[j];
279
- if (oUnique[Hash.qhash(p)] === undefined)
280
- af.push({type: 'Feature', properties: {id: `${af.length + 1}`}, geometry: {type: 'Polygon', coordinates: [p]}});
281
- }
282
- }
283
- }
284
- } */
285
233
  // HELPERS USED INTERNALLY
286
234
  // Get an individual test, so you can drive UI with the results.
287
235
  getTest(testID) {
@@ -1056,12 +1004,11 @@ function doAnalyzePlan(s, bLog = false) {
1056
1004
  s.bPostProcessingDone = false;
1057
1005
  }
1058
1006
  exports.doAnalyzePlan = doAnalyzePlan;
1007
+ //
1059
1008
  // Derive secondary analytics that are based on primary tests.
1060
1009
  // This concept allows Population Deviation to be a primary numeric test and
1061
1010
  // Equal Population to be secondary pass/fail validation.
1062
1011
  //
1063
- // NOTE - Should this be conditionalized on the test suites requested?
1064
- // Those are encapsulated in reports.ts right now, so not doing that.
1065
1012
  function doDeriveSecondaryTests(s, bLog = false) {
1066
1013
  s.tests[3 /* EqualPopulation */] = equal_1.doHasEqualPopulations(s, bLog);
1067
1014
  }
@@ -1265,14 +1212,12 @@ const Poly = __importStar(__webpack_require__(/*! @dra2020/poly */ "@dra2020/pol
1265
1212
  const Compactness = __importStar(__webpack_require__(/*! @dra2020/compactness */ "@dra2020/compactness"));
1266
1213
  const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
1267
1214
  // HELPER TO EXTRACT PROPERTIES OF DISTRICT SHAPES
1268
- // TODO - Create an array, as opposed to a dict
1269
1215
  function extractDistrictProperties(s, bLog = false) {
1270
1216
  // NOTE - I am assuming that district IDs are integers 1–N
1271
1217
  for (let i = 1; i <= s.state.nDistricts; i++) {
1272
1218
  const poly = s.districts.getDistrictShapeByID(i);
1273
1219
  // Guard against no shape for empty districts AND null shapes
1274
1220
  if (isAShape(poly)) {
1275
- // TODO - OPTIMIZE: Bundle these calls?
1276
1221
  const area = Poly.polyAreaFlat(poly);
1277
1222
  const perimeter = Poly.polyPerimeterFlat(poly);
1278
1223
  const diameter = Poly.polyDiameterFlat(poly);
@@ -1755,34 +1700,6 @@ function prepareRequirementsChecklist(s, bLog = false) {
1755
1700
  return paRequirements;
1756
1701
  }
1757
1702
  exports.prepareRequirementsChecklist = prepareRequirementsChecklist;
1758
- /* 10-23-2020 - Removed the district statistics sample.
1759
-
1760
- export const sampleDistrictStatistics: DistrictStatistics = {
1761
- table: [
1762
- // District 0 is the dummy unassigned district
1763
- // HACK - Total VAP #'s at the end are just so the same matches the type
1764
- [0, 0, 0, null, null, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
1765
- [1, 653133, -0.0950, T.TriState.Green, T.TriState.Red, T.TriState.Green, T.TriState.Green, 0.4177, 0.5823, 0.8631, 0.1369, 0.0734, 0.0360, 0.0009, 0.0235, 0.0064, 50000],
1766
- [2, 620961, -0.1396, T.TriState.Green, T.TriState.Red, T.TriState.Red, T.TriState.Green, 0.8820, 0.1180, 0.3129, 0.6871, 0.6169, 0.0391, 0.0013, 0.0310, 0.0099, 50000],
1767
- [3, 971777, 0.3465, T.TriState.Green, T.TriState.Red, T.TriState.Green, T.TriState.Green, 0.7261, 0.2739, 0.5174, 0.4826, 0.1745, 0.1572, 0.0020, 0.1531, 0.0090, 50000],
1768
- [4, 863420, 0.1964, T.TriState.Green, T.TriState.Red, T.TriState.Green, T.TriState.Green, 0.8957, 0.1043, 0.1734, 0.8266, 0.6489, 0.1348, 0.0020, 0.0496, 0.0127, 50000],
1769
- [5, 805029, 0.1155, T.TriState.Green, T.TriState.Red, T.TriState.Green, T.TriState.Yellow, 0.5743, 0.4257, 0.6587, 0.3413, 0.2494, 0.0363, 0.0012, 0.0536, 0.0081, 50000],
1770
- [6, 824741, 0.1428, T.TriState.Green, T.TriState.Red, T.TriState.Green, T.TriState.Red, 0.5341, 0.4659, 0.7045, 0.2955, 0.1619, 0.0526, 0.0018, 0.0782, 0.0090, 50000],
1771
- [7, 549714, -0.2383, T.TriState.Green, T.TriState.Green, T.TriState.Green, T.TriState.Green, 0.5025, 0.4975, 0.6906, 0.3094, 0.2468, 0.0319, 0.0013, 0.0258, 0.0111, 50000],
1772
- [8, 484777, -0.3283, T.TriState.Green, T.TriState.Green, T.TriState.Green, T.TriState.Green, 0.4105, 0.5895, 0.8370, 0.1630, 0.1074, 0.0316, 0.0013, 0.0197, 0.0077, 50000],
1773
- // District N+1 is the dummy state-summary district
1774
- [9, 721694, 0.6748, T.TriState.Green, T.TriState.Red, T.TriState.Red, T.TriState.Red, 0.6293, 0.3707, 0.5722, 0.4278, 0.2925, 0.0729, 0.0015, 0.0618, 0.0093, 400000]
1775
- ],
1776
- details: {},
1777
- datasets: {
1778
- shapes: "2010 VTD shapes",
1779
- census: "2010 Census Total Population",
1780
- vap: "2010 Voting Age Population",
1781
- election: "2016 Presidential, US Senate, Governor, and AG election results"
1782
- },
1783
- resources: {}
1784
- }
1785
- */
1786
1703
  // Create a DistrictStatistics instance, deep copying the underlying values.
1787
1704
  function prepareDistrictStatistics(s, bLog = false) {
1788
1705
  if (!(s.bPostProcessingDone)) {
@@ -1841,76 +1758,6 @@ function prepareDistrictStatistics(s, bLog = false) {
1841
1758
  return ds;
1842
1759
  }
1843
1760
  exports.prepareDistrictStatistics = prepareDistrictStatistics;
1844
- // META-DATA FOR TESTS/ANALYTICS
1845
- //
1846
- // NOTE - This structure is a vestige of having created a metadata-driven
1847
- // scorecard w/in district-analytics at first. It works for creating the
1848
- // unstyled results structures, so it isn't a high priority to rationalize.
1849
- var TestType;
1850
- (function (TestType) {
1851
- TestType[TestType["PassFail"] = 0] = "PassFail";
1852
- TestType[TestType["Percentage"] = 1] = "Percentage";
1853
- TestType[TestType["Number"] = 2] = "Number";
1854
- })(TestType || (TestType = {}));
1855
- const completeDefn = {
1856
- ID: 0 /* Complete */,
1857
- name: "Complete",
1858
- normalize: false,
1859
- externalType: TestType.PassFail,
1860
- suites: [0 /* Legal */]
1861
- };
1862
- const contiguousDefn = {
1863
- ID: 1 /* Contiguous */,
1864
- name: "Contiguous",
1865
- normalize: false,
1866
- externalType: TestType.PassFail,
1867
- suites: [0 /* Legal */]
1868
- };
1869
- const freeOfHolesDefn = {
1870
- ID: 2 /* FreeOfHoles */,
1871
- name: "Free of Holes",
1872
- normalize: false,
1873
- externalType: TestType.PassFail,
1874
- suites: [0 /* Legal */]
1875
- };
1876
- const equalPopulationDefn = {
1877
- ID: 3 /* EqualPopulation */,
1878
- name: "Equal Population",
1879
- normalize: false,
1880
- externalType: TestType.PassFail,
1881
- suites: [0 /* Legal */]
1882
- };
1883
- const populationDeviationDefn = {
1884
- ID: 4 /* PopulationDeviation */,
1885
- name: "Population Deviation",
1886
- normalize: true,
1887
- externalType: TestType.Percentage,
1888
- suites: [0 /* Legal */, 2 /* Best */] // Both so EqualPopulation can be assessed
1889
- };
1890
- const unexpectedCountySplitsDefn = {
1891
- ID: 5 /* UnexpectedCountySplits */,
1892
- name: "Unexpected County Splits",
1893
- normalize: false,
1894
- externalType: TestType.Percentage,
1895
- suites: [2 /* Best */]
1896
- };
1897
- const VTDSplitsDefn = {
1898
- ID: 6 /* VTDSplits */,
1899
- name: "VTD Splits",
1900
- normalize: false,
1901
- externalType: TestType.Number,
1902
- suites: [2 /* Best */]
1903
- };
1904
- // All the tests that have been defined (can be reported on)
1905
- const testDefns = {
1906
- [0 /* Complete */]: completeDefn,
1907
- [1 /* Contiguous */]: contiguousDefn,
1908
- [2 /* FreeOfHoles */]: freeOfHolesDefn,
1909
- [3 /* EqualPopulation */]: equalPopulationDefn,
1910
- [4 /* PopulationDeviation */]: populationDeviationDefn,
1911
- [5 /* UnexpectedCountySplits */]: unexpectedCountySplitsDefn,
1912
- [6 /* VTDSplits */]: VTDSplitsDefn,
1913
- };
1914
1761
  // Postprocess analytics - Normalize numeric results and derive secondary tests.
1915
1762
  // Do this after analytics have been run and before preparing a test log or scorecard.
1916
1763
  function doAnalyzePostProcessing(s, bLog = false) {
@@ -2148,7 +1995,6 @@ exports.PRECISION = 4;
2148
1995
  exports.NORMALIZED_RANGE = 100;
2149
1996
  // The dummy district ID for features not assigned districts yet
2150
1997
  exports.NOT_ASSIGNED = 0;
2151
- // TODO - DASHBOARD: Discuss w/ Dave
2152
1998
  // # of items to report as problematic (e.g., features, districts, etc.)
2153
1999
  exports.NUMBER_OF_ITEMS_TO_REPORT = 10;
2154
2000
  // The virtual geoID for "neighbors" in other states
@@ -2282,28 +2128,6 @@ function isUninhabited(geoID, s) {
2282
2128
  return bUninhabited;
2283
2129
  }
2284
2130
  exports.isUninhabited = isUninhabited;
2285
- // NORMALIZING RESULTS
2286
- function normalize(rawScore, testScale) {
2287
- let rangeMin = testScale.scale[0];
2288
- let rangeMax = testScale.scale[1];
2289
- // Invert the raw value if necessary to make bigger = better
2290
- // TODO - This works for Population Deviation, because the max is 1.0.
2291
- // Generalize this???
2292
- if (testScale.bInvertRaw) {
2293
- rawScore = 1.0 - rawScore;
2294
- }
2295
- // Coerce the value to be w/in the given range
2296
- let coercedValue = Math.min(Math.max(rawScore, rangeMin), rangeMax);
2297
- // Scale the bounded value w/in the range [0 - (rangeMax - rangeMin)]
2298
- let scaledValue = (coercedValue - rangeMin) / (rangeMax - rangeMin);
2299
- // Invert the scaled value if necessary to make bigger = better
2300
- if (testScale.bInvertScaled) {
2301
- scaledValue = 1.0 - scaledValue;
2302
- }
2303
- // Finally, make the range [0-100]
2304
- return Math.round(scaledValue * S.NORMALIZED_RANGE);
2305
- }
2306
- exports.normalize = normalize;
2307
2131
  // Round a fractional number [0-1] to the desired level of PRECISION.
2308
2132
  function trim(fullFraction, digits = undefined) {
2309
2133
  if (digits == 0) {
@@ -2341,14 +2165,11 @@ function andArray(arr) {
2341
2165
  }
2342
2166
  exports.andArray = andArray;
2343
2167
  // WORKING WITH OBJECT KEYS/PROPERTIES
2344
- // TODO - TERRY, is this copesetic?
2345
- // TODO - Handle integer keys?
2346
2168
  // Does an object have a key/property?
2347
2169
  function keyExists(k, o) {
2348
2170
  return k in o;
2349
2171
  }
2350
2172
  exports.keyExists = keyExists;
2351
- // TODO - TERRY, can these three be combined into a generic isEmpty() check?
2352
2173
  // Does an object (dict) have any keys/properties?
2353
2174
  function isObjectEmpty(o) {
2354
2175
  return Object.keys(o).length === 0;
@@ -2375,7 +2196,6 @@ function getObjectKeys(o) {
2375
2196
  return Object.keys(o);
2376
2197
  }
2377
2198
  exports.getObjectKeys = getObjectKeys;
2378
- // TODO - Convert getNumericObjectKeys() idiom to for..of where possible
2379
2199
  function getNumericObjectKeys(o) {
2380
2200
  return Object.keys(o).map(Number);
2381
2201
  }
@@ -2447,7 +2267,6 @@ function mapBooleanToTriState(bool) {
2447
2267
  return (bool) ? 0 /* Green */ : 2 /* Red */;
2448
2268
  }
2449
2269
  exports.mapBooleanToTriState = mapBooleanToTriState;
2450
- // TODO - TERRY: What is this the simple explanation of what this thing is doing?
2451
2270
  var util_1 = __webpack_require__(/*! @dra2020/util */ "@dra2020/util");
2452
2271
  exports.depthof = util_1.depthof;
2453
2272
 
@@ -2593,38 +2412,6 @@ function doIsContiguous(s, bLog = false) {
2593
2412
  return test;
2594
2413
  }
2595
2414
  exports.doIsContiguous = doIsContiguous;
2596
- /* GRAPH - Removed 10/05/2020. Using dra-graph instead.
2597
- // Are the features in a district fully connected?
2598
- export function isConnected(districtGeos: Set<string>, graph: D.Graph): boolean
2599
- {
2600
- // TODO - TERRY, why does this constructor need a <T> type specification?
2601
- let visited = new Set<string>();
2602
- let toProcess: string[] = [];
2603
-
2604
- // Start processing with the first geoID in the district
2605
- let iter = districtGeos.values();
2606
- toProcess.push(iter.next().value);
2607
-
2608
- // While there are geoIDs in the district that haven't been processed
2609
- while (toProcess.length > 0)
2610
- {
2611
- // Grab a geoID and process it
2612
- let node = toProcess.pop() as string;
2613
- visited.add(node);
2614
-
2615
- // Get its actual, in-state neighbors
2616
- let actualNeighbors = graph.peerNeighbors(node).filter(x => U.isInState(x));
2617
-
2618
- // Add neighbors to visit, if they're in the same district Y haven't already been visited
2619
- let neighborsToVisit = actualNeighbors.filter(x => districtGeos.has(x) && (!visited.has(x)));
2620
- // TODO - TERRY, is this the quickest/best way to do this?
2621
- toProcess.push(...neighborsToVisit);
2622
- }
2623
-
2624
- // Stop when you've visited all the geoIDs in the district
2625
- return visited.size == districtGeos.size;
2626
- }
2627
- */
2628
2415
  //
2629
2416
  // FREE OF HOLES - Are any districts fully embedded w/in another district?
2630
2417
  //
@@ -2635,9 +2422,6 @@ export function isConnected(districtGeos: Set<string>, graph: D.Graph): boolean
2635
2422
  // To test this, load the NC 2010 map 'SAMPLE-BG-map-hole.csv'. District 1,
2636
2423
  // Buncombe County (37021), is a donut hole w/in District 3.
2637
2424
  //
2638
- // TODO - OPTIMIZE: This to take advantage of district boundary info, if/when
2639
- // we cache one to optimize compactness.
2640
- //
2641
2425
  function doIsFreeOfHoles(s, bLog = false) {
2642
2426
  let test = s.getTest(2 /* FreeOfHoles */);
2643
2427
  // Initialize values
@@ -2667,89 +2451,6 @@ function doIsFreeOfHoles(s, bLog = false) {
2667
2451
  return test;
2668
2452
  }
2669
2453
  exports.doIsFreeOfHoles = doIsFreeOfHoles;
2670
- /* GRAPH - Removed 10/05/2020. Using dra-graph instead.
2671
- // Test whether one district is embedded w/in any other.
2672
- export function isEmbedded(districtID: number, geoIDs: Set<string>, plan: D.Plan, graph: D.Graph): boolean
2673
- {
2674
- // NOTE - "features" here = "geoIDs." These aren't "features" proper, just
2675
- // identifier strings.
2676
- let features = geoIDs;
2677
- let planByGeo = plan.byGeoID();
2678
-
2679
- // Assume the district is embedded
2680
- let bEmbedded = true;
2681
- // Keep track of the neighoring districts
2682
- let neighboringDistricts = new Set();
2683
-
2684
- // TODO - OPTIMIZE: Use just the boundary features, when available
2685
- // Get the features for the real district
2686
- let featuresToCheck = Array.from(features);
2687
-
2688
- // If the district has features, check whether it is embedded
2689
- if (!(U.isArrayEmpty(featuresToCheck)))
2690
- {
2691
- // For each feature that needs to be checked (see above)
2692
- for (let feature of featuresToCheck)
2693
- {
2694
- // Get its neighbors (including the virtual "out of state" ones)
2695
- let neighbors = graph.peerNeighbors(feature);
2696
-
2697
- for (let neighbor of neighbors)
2698
- {
2699
- if (U.isOutOfState(neighbor))
2700
- {
2701
- bEmbedded = false;
2702
- // No need to check any more neighbors
2703
- break;
2704
- }
2705
- else
2706
- {
2707
- let neighboringDistrict = U.getDistrict(planByGeo, neighbor);
2708
-
2709
- // Assume that a missing district assignment (= None) means that the
2710
- // feature is "water-only" AND part of the state border (vs. internal)
2711
- // and, therefore, not in the plan/map.
2712
-
2713
- if (neighboringDistrict == undefined)
2714
- {
2715
- bEmbedded = false;
2716
- // No need to check any more neighbors
2717
- break;
2718
- }
2719
- else
2720
- {
2721
- // TODO - OPTIMIZE: Since we're checking *all* features in a district right
2722
- // now, not just boundary features and neighbors in other districts,
2723
- // prune out the current district. If/when we optimize compactness
2724
- // to cache district boundaries (as before in my Python implementation),
2725
- // we won't have to guard adding "neighboring" districts in this way.
2726
- if (neighboringDistrict != districtID)
2727
- {
2728
- neighboringDistricts.add(neighboringDistrict);
2729
- }
2730
-
2731
- if (neighboringDistricts.size > 1)
2732
- {
2733
- bEmbedded = false;
2734
- // No need to check any more neighbors
2735
- break;
2736
- }
2737
- }
2738
- }
2739
- }
2740
- // If a district is not embedded, there's no need to check anymore
2741
- // border geos.
2742
- if (!bEmbedded)
2743
- {
2744
- break;
2745
- }
2746
- }
2747
-
2748
- }
2749
-
2750
- return bEmbedded;
2751
- }
2752
- */
2753
2454
 
2754
2455
 
2755
2456
  /***/ }),