@dra2020/district-analytics 5.0.2 → 5.2.0

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.
@@ -122,7 +122,7 @@ const analyze_1 = __webpack_require__(/*! ./analyze */ "./src/analyze.ts");
122
122
  const score_1 = __webpack_require__(/*! ./score */ "./src/score.ts");
123
123
  const results_1 = __webpack_require__(/*! ./results */ "./src/results.ts");
124
124
  const results_2 = __webpack_require__(/*! ./results */ "./src/results.ts");
125
- const geofeature_1 = __webpack_require__(/*! ./geofeature */ "./src/geofeature.ts");
125
+ const Poly = __importStar(__webpack_require__(/*! @dra2020/poly */ "@dra2020/poly"));
126
126
  const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
127
127
  const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
128
128
  class AnalyticsSession {
@@ -198,7 +198,7 @@ class AnalyticsSession {
198
198
  // If a district has a shape & it is not contiguous, by definition,
199
199
  // it will be a Multipolygon, i.e., it will have multiple pieces, some
200
200
  // possibly embedded w/in other districts. Get & add all the pieces.
201
- const districtParts = geofeature_1.polyParts(poly);
201
+ const districtParts = Poly.polyParts(poly);
202
202
  discontiguousDistrictFeatures.features.push(...districtParts.features);
203
203
  // discontiguousDistrictFeatures.features.push(poly);
204
204
  }
@@ -969,7 +969,7 @@ Compactness: None
969
969
  Proportionality: 27.67% gap
970
970
  Cohesiveness: 11 unexpected splits, affecting 27.14% of the total population
971
971
 
972
- These counties are split unexpectedly:
972
+ These counties are split unexpectedly (meaning that they're smaller than a district):
973
973
 
974
974
  • Bladen
975
975
  • Buncombe
@@ -1073,6 +1073,7 @@ function doFindCountiesSplitUnexpectedly(s, bLog = false) {
1073
1073
  }
1074
1074
  countiesSplitUnexpectedly = countiesSplitUnexpectedly.sort();
1075
1075
  test['score'] = U.trim(unexpectedAffected);
1076
+ test['details']['nSplits'] = nSplits;
1076
1077
  test['details']['unexpectedSplits'] = unexpectedSplits;
1077
1078
  test['details']['countiesSplitUnexpectedly'] = countiesSplitUnexpectedly;
1078
1079
  return test;
@@ -1113,7 +1114,6 @@ var __importStar = (this && this.__importStar) || function (mod) {
1113
1114
  Object.defineProperty(exports, "__esModule", { value: true });
1114
1115
  // NOTE - This file will NOT be empty, when legacy code is deleted.
1115
1116
  const Poly = __importStar(__webpack_require__(/*! @dra2020/poly */ "@dra2020/poly"));
1116
- const geofeature_1 = __webpack_require__(/*! ./geofeature */ "./src/geofeature.ts");
1117
1117
  // HELPER TO EXTRACT PROPERTIES OF DISTRICT SHAPES
1118
1118
  // TODO - Create an array, as opposed to a dict
1119
1119
  function extractDistrictProperties(s, bLog = false) {
@@ -1121,13 +1121,12 @@ function extractDistrictProperties(s, bLog = false) {
1121
1121
  for (let i = 1; i <= s.state.nDistricts; i++) {
1122
1122
  let poly = s.districts.getDistrictShapeByID(i);
1123
1123
  // Guard against no shape for empty districts AND null shapes
1124
- let polyOptions = { noLatitudeCorrection: true };
1125
- let bNull = (!Poly.polyNormalize(poly, polyOptions));
1124
+ let bNull = !Poly.polyNormalize(poly);
1126
1125
  if (poly && (!bNull)) {
1127
1126
  // TODO - OPTIMIZE: Bundle these calls?
1128
- let area = geofeature_1.gfArea(poly);
1129
- let perimeter = geofeature_1.gfPerimeter(poly);
1130
- let diameter = geofeature_1.gfDiameter(poly);
1127
+ let area = Poly.polyAreaFlat(poly);
1128
+ let perimeter = Poly.polyPerimeterFlat(poly);
1129
+ let diameter = Poly.polyDiameterFlat(poly);
1131
1130
  let props = [0, 0, 0]; // TODO - TERRY?!?
1132
1131
  props[0 /* Area */] = area;
1133
1132
  props[1 /* Diameter */] = diameter;
@@ -1218,143 +1217,6 @@ function doHasEqualPopulations(s, bLog = false) {
1218
1217
  exports.doHasEqualPopulations = doHasEqualPopulations;
1219
1218
 
1220
1219
 
1221
- /***/ }),
1222
-
1223
- /***/ "./src/geofeature.ts":
1224
- /*!***************************!*\
1225
- !*** ./src/geofeature.ts ***!
1226
- \***************************/
1227
- /*! no static exports found */
1228
- /***/ (function(module, exports, __webpack_require__) {
1229
-
1230
- "use strict";
1231
-
1232
- //
1233
- // GEO-FEATURES UTILITIES
1234
- //
1235
- var __importStar = (this && this.__importStar) || function (mod) {
1236
- if (mod && mod.__esModule) return mod;
1237
- var result = {};
1238
- if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
1239
- result["default"] = mod;
1240
- return result;
1241
- };
1242
- Object.defineProperty(exports, "__esModule", { value: true });
1243
- const Poly = __importStar(__webpack_require__(/*! @dra2020/poly */ "@dra2020/poly"));
1244
- // HELPER
1245
- function polyParts(poly) {
1246
- let parts = { type: 'FeatureCollection', features: [] };
1247
- let af = parts.features;
1248
- if (poly.geometry.type === 'MultiPolygon') {
1249
- // Push all non-contiguous polygons
1250
- for (let j = 0; j < poly.geometry.coordinates.length; j++) {
1251
- let onePoly = poly.geometry.coordinates[j];
1252
- af.push({ type: 'Feature', properties: { id: `${af.length + 1}` }, geometry: { type: 'Polygon', coordinates: onePoly } });
1253
- }
1254
- }
1255
- else {
1256
- parts.features.push(poly);
1257
- }
1258
- return parts;
1259
- }
1260
- exports.polyParts = polyParts;
1261
- // CARTESIAN SHIMS OVER 'POLY' FUNCTIONS
1262
- // TODO - POLY: Confirm Cartesian calculations
1263
- function gfArea(poly) {
1264
- let area = _polygonArea(poly);
1265
- return area;
1266
- }
1267
- exports.gfArea = gfArea;
1268
- // Algorithm for the area of a simple/single planar polygon:
1269
- // https://algorithmtutor.com/Computational-Geometry/Area-of-a-polygon-given-a-set-of-points/
1270
- // https://mathopenref.com/coordpolygonarea2.html
1271
- //
1272
- // function polygonArea(X, Y, numPoints)
1273
- // {
1274
- // area = 0; // Accumulates area in the loop
1275
- // j = numPoints-1; // The last vertex is the 'previous' one to the first
1276
- // for (i=0; i<numPoints; i++)
1277
- // { area = area + (X[j]+X[i]) * (Y[j]-Y[i]);
1278
- // j = i; //j is previous vertex to i
1279
- // }
1280
- // return area/2;
1281
- // }
1282
- // Reimplemented to use polygons vs. X & Y vectors
1283
- function _polygonSimpleArea(p) {
1284
- let area = 0; // Accumulates area in the loop
1285
- let n = p.length;
1286
- let j = n - 1; // The last vertex is the 'previous' one to the first
1287
- for (let i = 0; i < n; i++) {
1288
- area = area + (p[j][0] + p[i][0]) * (p[j][1] - p[i][1]);
1289
- // area = area + (X[j] + X[i]) * (Y[j] - Y[i]);
1290
- j = i; // j is previous vertex to i
1291
- }
1292
- return Math.abs(area / 2);
1293
- }
1294
- // Generalizes the above for MultiPolygons -- cloned from polyArea() in 'poly'
1295
- function _polygonArea(poly) {
1296
- let polyOptions = { noLatitudeCorrection: true }; // NO-OP?
1297
- poly = Poly.polyNormalize(poly, polyOptions); // TODO - POLY: Discuss w/ Terry
1298
- let a = 0;
1299
- // A MultiPolygon is a set of polygons
1300
- for (let i = 0; poly && i < poly.length; i++) {
1301
- // A single polygon is an exterior ring with interior holes. Holes are subtracted.
1302
- let p = poly[i];
1303
- for (let j = 0; j < p.length; j++) {
1304
- let sp = p[j];
1305
- a += _polygonSimpleArea(sp) * (j == 0 ? 1 : -1);
1306
- }
1307
- }
1308
- return a;
1309
- }
1310
- // TODO - POLY: Confirm Cartesian calculations w/ Terry
1311
- // The perimeter calculation already just computes cartesian distance if you
1312
- // pass in the noLatitudeCorrection flag. You would need to divide by
1313
- // Poly.EARTH_RADIUS to go from the returned units of meters to Lat/Lon “units”.
1314
- function gfPerimeter(poly) {
1315
- let perimeter = _polygonPerimeter(poly);
1316
- return perimeter;
1317
- }
1318
- exports.gfPerimeter = gfPerimeter;
1319
- // TODO - POLY: Confirm Cartesian calculations w/ Terry
1320
- // Cloned from polyPerimeter() in 'poly' and revised to use Cartesian distance
1321
- // NOTE - No conversion of degrees to radians!
1322
- function _polygonPerimeter(poly) {
1323
- let polyOptions = { noLatitudeCorrection: true }; // Cartesian distance
1324
- poly = Poly.polyNormalize(poly, polyOptions);
1325
- let perimeter = 0;
1326
- for (let i = 0; poly && i < poly.length; i++) {
1327
- // Ignore holes so only look at first polyline
1328
- let p = poly[i][0];
1329
- for (let j = 0; j < p.length - 1; j++)
1330
- perimeter += _distance(p[j][0], p[j][1], p[j + 1][0], p[j + 1][1]);
1331
- if (p.length > 2 && (p[0][0] != p[p.length - 1][0] || p[0][1] != p[p.length - 1][1]))
1332
- perimeter += _distance(p[0][0], p[0][1], p[p.length - 1][0], p[p.length - 1][1]);
1333
- }
1334
- return perimeter;
1335
- }
1336
- function _distance(x1, y1, x2, y2) {
1337
- let dLat = y2 - y1;
1338
- let dLon = x2 - x1;
1339
- let d;
1340
- d = Math.sqrt((dLat * dLat) + (dLon * dLon));
1341
- return d;
1342
- }
1343
- // TODO - POLY: Confirm Cartesian calculations w/ Terry
1344
- // As I mentioned, the polyCircle code was already just treating the coordinate
1345
- // system as Cartesian. I then did polyFromCircle to convert it to a polygon that
1346
- // then could be passed to polyArea in order to take into account the projection.
1347
- // If instead, you compute area directly from the circle as PI R squared, then
1348
- // you should have your cartesian circular area.
1349
- function gfDiameter(poly) {
1350
- let polyOptions = { noLatitudeCorrection: true }; // NO-OP
1351
- let circle = Poly.polyToCircle(poly, polyOptions);
1352
- let diameter = circle.r * 2;
1353
- return diameter;
1354
- }
1355
- exports.gfDiameter = gfDiameter;
1356
-
1357
-
1358
1220
  /***/ }),
1359
1221
 
1360
1222
  /***/ "./src/index.ts":
@@ -1851,6 +1713,7 @@ function doAnalyzePostProcessing(s, bLog = false) {
1851
1713
  scorecard.traditionalPrinciples.details['census'] = datasets.census;
1852
1714
  const simpleSplits = s.getTest(5 /* UnexpectedCountySplits */);
1853
1715
  scorecard.traditionalPrinciples.details['unexpectedAffected'] = simpleSplits['score'];
1716
+ scorecard.traditionalPrinciples.details['nSplits'] = simpleSplits['details']['nSplits'];
1854
1717
  scorecard.traditionalPrinciples.details['countiesSplitUnexpectedly'] = U.deepCopy(simpleSplits['details']['countiesSplitUnexpectedly']);
1855
1718
  // NOTE - Add split precincts in dra-client directly
1856
1719
  // Derive secondary tests