@dra2020/district-analytics 10.0.4 → 10.0.7
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.
|
@@ -143,11 +143,6 @@ class AnalyticsSession {
|
|
|
143
143
|
this.districts = new D.Districts(this, SessionRequest['districtShapes']);
|
|
144
144
|
}
|
|
145
145
|
processConfig(config) {
|
|
146
|
-
// NOTE - Session settings are required:
|
|
147
|
-
// - Analytics suites can be defaulted to all with [], but
|
|
148
|
-
// - Dataset keys must be explicitly specified with 'dataset'
|
|
149
|
-
// NOTE - Legacy feature: Always calc everything
|
|
150
|
-
config['suites'] = [0 /* Legal */, 1 /* Fair */, 2 /* Best */];
|
|
151
146
|
// Default the Census & redistricting cycle to 2010
|
|
152
147
|
if (!(U.keyExists('cycle', config)))
|
|
153
148
|
config['cycle'] = 2010;
|
|
@@ -230,58 +225,6 @@ class AnalyticsSession {
|
|
|
230
225
|
// 1. All polygons in a multi-polygon; and
|
|
231
226
|
// 2. All holes in a otherwise cohesive polygon.
|
|
232
227
|
// 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
228
|
// HELPERS USED INTERNALLY
|
|
286
229
|
// Get an individual test, so you can drive UI with the results.
|
|
287
230
|
getTest(testID) {
|
|
@@ -1056,12 +999,11 @@ function doAnalyzePlan(s, bLog = false) {
|
|
|
1056
999
|
s.bPostProcessingDone = false;
|
|
1057
1000
|
}
|
|
1058
1001
|
exports.doAnalyzePlan = doAnalyzePlan;
|
|
1002
|
+
//
|
|
1059
1003
|
// Derive secondary analytics that are based on primary tests.
|
|
1060
1004
|
// This concept allows Population Deviation to be a primary numeric test and
|
|
1061
1005
|
// Equal Population to be secondary pass/fail validation.
|
|
1062
1006
|
//
|
|
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
1007
|
function doDeriveSecondaryTests(s, bLog = false) {
|
|
1066
1008
|
s.tests[3 /* EqualPopulation */] = equal_1.doHasEqualPopulations(s, bLog);
|
|
1067
1009
|
}
|
|
@@ -1265,14 +1207,12 @@ const Poly = __importStar(__webpack_require__(/*! @dra2020/poly */ "@dra2020/pol
|
|
|
1265
1207
|
const Compactness = __importStar(__webpack_require__(/*! @dra2020/compactness */ "@dra2020/compactness"));
|
|
1266
1208
|
const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
|
|
1267
1209
|
// HELPER TO EXTRACT PROPERTIES OF DISTRICT SHAPES
|
|
1268
|
-
// TODO - Create an array, as opposed to a dict
|
|
1269
1210
|
function extractDistrictProperties(s, bLog = false) {
|
|
1270
1211
|
// NOTE - I am assuming that district IDs are integers 1–N
|
|
1271
1212
|
for (let i = 1; i <= s.state.nDistricts; i++) {
|
|
1272
1213
|
const poly = s.districts.getDistrictShapeByID(i);
|
|
1273
1214
|
// Guard against no shape for empty districts AND null shapes
|
|
1274
1215
|
if (isAShape(poly)) {
|
|
1275
|
-
// TODO - OPTIMIZE: Bundle these calls?
|
|
1276
1216
|
const area = Poly.polyAreaFlat(poly);
|
|
1277
1217
|
const perimeter = Poly.polyPerimeterFlat(poly);
|
|
1278
1218
|
const diameter = Poly.polyDiameterFlat(poly);
|
|
@@ -1755,34 +1695,6 @@ function prepareRequirementsChecklist(s, bLog = false) {
|
|
|
1755
1695
|
return paRequirements;
|
|
1756
1696
|
}
|
|
1757
1697
|
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
1698
|
// Create a DistrictStatistics instance, deep copying the underlying values.
|
|
1787
1699
|
function prepareDistrictStatistics(s, bLog = false) {
|
|
1788
1700
|
if (!(s.bPostProcessingDone)) {
|
|
@@ -1841,76 +1753,6 @@ function prepareDistrictStatistics(s, bLog = false) {
|
|
|
1841
1753
|
return ds;
|
|
1842
1754
|
}
|
|
1843
1755
|
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
1756
|
// Postprocess analytics - Normalize numeric results and derive secondary tests.
|
|
1915
1757
|
// Do this after analytics have been run and before preparing a test log or scorecard.
|
|
1916
1758
|
function doAnalyzePostProcessing(s, bLog = false) {
|
|
@@ -2148,7 +1990,6 @@ exports.PRECISION = 4;
|
|
|
2148
1990
|
exports.NORMALIZED_RANGE = 100;
|
|
2149
1991
|
// The dummy district ID for features not assigned districts yet
|
|
2150
1992
|
exports.NOT_ASSIGNED = 0;
|
|
2151
|
-
// TODO - DASHBOARD: Discuss w/ Dave
|
|
2152
1993
|
// # of items to report as problematic (e.g., features, districts, etc.)
|
|
2153
1994
|
exports.NUMBER_OF_ITEMS_TO_REPORT = 10;
|
|
2154
1995
|
// The virtual geoID for "neighbors" in other states
|
|
@@ -2282,28 +2123,6 @@ function isUninhabited(geoID, s) {
|
|
|
2282
2123
|
return bUninhabited;
|
|
2283
2124
|
}
|
|
2284
2125
|
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
2126
|
// Round a fractional number [0-1] to the desired level of PRECISION.
|
|
2308
2127
|
function trim(fullFraction, digits = undefined) {
|
|
2309
2128
|
if (digits == 0) {
|
|
@@ -2341,14 +2160,11 @@ function andArray(arr) {
|
|
|
2341
2160
|
}
|
|
2342
2161
|
exports.andArray = andArray;
|
|
2343
2162
|
// WORKING WITH OBJECT KEYS/PROPERTIES
|
|
2344
|
-
// TODO - TERRY, is this copesetic?
|
|
2345
|
-
// TODO - Handle integer keys?
|
|
2346
2163
|
// Does an object have a key/property?
|
|
2347
2164
|
function keyExists(k, o) {
|
|
2348
2165
|
return k in o;
|
|
2349
2166
|
}
|
|
2350
2167
|
exports.keyExists = keyExists;
|
|
2351
|
-
// TODO - TERRY, can these three be combined into a generic isEmpty() check?
|
|
2352
2168
|
// Does an object (dict) have any keys/properties?
|
|
2353
2169
|
function isObjectEmpty(o) {
|
|
2354
2170
|
return Object.keys(o).length === 0;
|
|
@@ -2375,7 +2191,6 @@ function getObjectKeys(o) {
|
|
|
2375
2191
|
return Object.keys(o);
|
|
2376
2192
|
}
|
|
2377
2193
|
exports.getObjectKeys = getObjectKeys;
|
|
2378
|
-
// TODO - Convert getNumericObjectKeys() idiom to for..of where possible
|
|
2379
2194
|
function getNumericObjectKeys(o) {
|
|
2380
2195
|
return Object.keys(o).map(Number);
|
|
2381
2196
|
}
|
|
@@ -2447,7 +2262,6 @@ function mapBooleanToTriState(bool) {
|
|
|
2447
2262
|
return (bool) ? 0 /* Green */ : 2 /* Red */;
|
|
2448
2263
|
}
|
|
2449
2264
|
exports.mapBooleanToTriState = mapBooleanToTriState;
|
|
2450
|
-
// TODO - TERRY: What is this the simple explanation of what this thing is doing?
|
|
2451
2265
|
var util_1 = __webpack_require__(/*! @dra2020/util */ "@dra2020/util");
|
|
2452
2266
|
exports.depthof = util_1.depthof;
|
|
2453
2267
|
|
|
@@ -2593,38 +2407,6 @@ function doIsContiguous(s, bLog = false) {
|
|
|
2593
2407
|
return test;
|
|
2594
2408
|
}
|
|
2595
2409
|
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
2410
|
//
|
|
2629
2411
|
// FREE OF HOLES - Are any districts fully embedded w/in another district?
|
|
2630
2412
|
//
|
|
@@ -2635,9 +2417,6 @@ export function isConnected(districtGeos: Set<string>, graph: D.Graph): boolean
|
|
|
2635
2417
|
// To test this, load the NC 2010 map 'SAMPLE-BG-map-hole.csv'. District 1,
|
|
2636
2418
|
// Buncombe County (37021), is a donut hole w/in District 3.
|
|
2637
2419
|
//
|
|
2638
|
-
// TODO - OPTIMIZE: This to take advantage of district boundary info, if/when
|
|
2639
|
-
// we cache one to optimize compactness.
|
|
2640
|
-
//
|
|
2641
2420
|
function doIsFreeOfHoles(s, bLog = false) {
|
|
2642
2421
|
let test = s.getTest(2 /* FreeOfHoles */);
|
|
2643
2422
|
// Initialize values
|
|
@@ -2667,89 +2446,6 @@ function doIsFreeOfHoles(s, bLog = false) {
|
|
|
2667
2446
|
return test;
|
|
2668
2447
|
}
|
|
2669
2448
|
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
2449
|
|
|
2754
2450
|
|
|
2755
2451
|
/***/ }),
|