diff --git a/CHANGELOG.md b/CHANGELOG.md
index 547f58d1b..af023b5e2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
### Fixed
+## [1.12.1] - 2021-10-29
+### Fixed
+- Fix import for regions that have a geounit with 1 identical geometry nested under it [#1053](https://github.com/PublicMapping/districtbuilder/pull/1053)
+- Fix simplification for geojson with invalid geometries [#1054](https://github.com/PublicMapping/districtbuilder/pull/1054)
+- Fix population deviation tooltip [#1055](https://github.com/PublicMapping/districtbuilder/pull/1055)
+
## [1.12.0] - 2021-10-25
### Changed
- Support negative/adjusted population values [#1043](https://github.com/PublicMapping/districtbuilder/pull/1043)
@@ -355,7 +361,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Initial release.
-[unreleased]: https://github.com/publicmapping/districtbuilder/compare/1.12.0...HEAD
+[unreleased]: https://github.com/publicmapping/districtbuilder/compare/1.12.1...HEAD
+[1.12.1]: https://github.com/publicmapping/districtbuilder/compare/1.12.0...1.12.1
[1.12.0]: https://github.com/publicmapping/districtbuilder/compare/1.11.0...1.12.0
[1.11.0]: https://github.com/publicmapping/districtbuilder/compare/1.10.1...1.11.0
[1.10.1]: https://github.com/publicmapping/districtbuilder/compare/1.10.0...1.10.1
diff --git a/src/client/components/ProjectSidebar.tsx b/src/client/components/ProjectSidebar.tsx
index d06333512..d4f4b9a5d 100644
--- a/src/client/components/ProjectSidebar.tsx
+++ b/src/client/components/ProjectSidebar.tsx
@@ -670,7 +670,7 @@ const SidebarRow = memo(
Add{" "}
{Math.floor(
- Math.abs(intermediateDeviation) + popDeviationThreshold
+ Math.abs(intermediateDeviation) - popDeviationThreshold
).toLocaleString()}{" "}
people to this district to meet the {popDeviation}% population deviation
tolerance
diff --git a/src/server/global.d.ts b/src/server/global.d.ts
index bfd84040b..0dd8bb3be 100644
--- a/src/server/global.d.ts
+++ b/src/server/global.d.ts
@@ -14,3 +14,9 @@ declare module "geojson2shp" {
options?: ConvertOptions
): Promise
;
}
+
+declare module "simplify-geojson" {
+ import { GeoJSON } from "geojson";
+
+ export function simplify(feature: GeoJSON, tolerance?: number): void;
+}
diff --git a/src/server/migrations/1635458920929-add_postgis.ts b/src/server/migrations/1635458920929-add_postgis.ts
new file mode 100644
index 000000000..1bde8dfc5
--- /dev/null
+++ b/src/server/migrations/1635458920929-add_postgis.ts
@@ -0,0 +1,13 @@
+import { MigrationInterface, QueryRunner } from "typeorm";
+
+export class addPostgis1635458920929 implements MigrationInterface {
+ name = "addPostgis1635458920929";
+
+ public async up(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(`CREATE EXTENSION IF NOT EXISTS "postgis"`);
+ }
+
+ public async down(queryRunner: QueryRunner): Promise {
+ await queryRunner.query(`DROP EXTENSION IF EXISTS "postgis"`);
+ }
+}
diff --git a/src/server/package.json b/src/server/package.json
index bccd322c1..4f51416d4 100644
--- a/src/server/package.json
+++ b/src/server/package.json
@@ -42,7 +42,6 @@
"@turf/bbox": "^6.5.0",
"@turf/length": "6.0.2",
"@turf/polygon-to-line": "6.0.3",
- "@turf/simplify": "^6.5.0",
"aws-sdk": "2.616.0",
"base64url": "3.0.1",
"bcrypt": "5.0.0",
diff --git a/src/server/src/districts/entities/geo-unit-topology.entity.ts b/src/server/src/districts/entities/geo-unit-topology.entity.ts
index ae571a85a..c367c80c9 100644
--- a/src/server/src/districts/entities/geo-unit-topology.entity.ts
+++ b/src/server/src/districts/entities/geo-unit-topology.entity.ts
@@ -300,7 +300,9 @@ export class GeoUnitTopology {
// Keep recursing into the hierarchy until we reach the end
const results = mapToDefinition(hierarchyNumOrArray);
// Simplify if possible
- return results.every(item => item === results[0]) ? results[0] : results;
+ return results.length !== 1 && results.every(item => item === results[0])
+ ? results[0]
+ : results;
}
});
diff --git a/src/server/src/projects/services/projects.service.ts b/src/server/src/projects/services/projects.service.ts
index 33bb5031e..2c21b0061 100644
--- a/src/server/src/projects/services/projects.service.ts
+++ b/src/server/src/projects/services/projects.service.ts
@@ -1,11 +1,7 @@
-import { Injectable, Logger } from "@nestjs/common";
+import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { TypeOrmCrudService } from "@nestjsx/crud-typeorm";
-import simplify from "@turf/simplify";
-import bbox from "@turf/bbox";
-import { BBox } from "@turf/helpers";
import { Repository, SelectQueryBuilder, DeepPartial } from "typeorm";
-import _ from "lodash";
import { Project } from "../entities/project.entity";
import { ProjectVisibility } from "../../../../shared/constants";
@@ -19,8 +15,6 @@ type AllProjectsOptions = IPaginationOptions & {
@Injectable()
export class ProjectsService extends TypeOrmCrudService {
- private readonly logger = new Logger(ProjectsService.name);
-
constructor(@InjectRepository(Project) repo: Repository) {
super(repo);
}
@@ -31,62 +25,47 @@ export class ProjectsService extends TypeOrmCrudService {
}
getProjectsBase(): SelectQueryBuilder {
- return this.repo
- .createQueryBuilder("project")
- .innerJoin("project.regionConfig", "regionConfig")
- .innerJoin("project.user", "user")
- .leftJoin("project.chamber", "chamber")
- .select([
- "project.id",
- "project.name",
- "project.numberOfDistricts",
- "project.updatedDt",
- "project.createdDt",
- "project.districts",
- "regionConfig.name",
- "regionConfig.id",
- "user.id",
- "user.name"
- ])
- .orderBy("project.updatedDt", "DESC");
- }
-
- computeBBoxArea(project: Project): number {
- const box: BBox = bbox(project.districts);
- return (box[2] - box[0]) * (box[3] - box[1]);
- }
-
- // We only use the districts column for displaying a mini-map outside of the main Project Screen
- // so we can simplify the geometries to save on size and improve performance
- async simplifyDistricts(page: Promise>): Promise> {
- const projects = await page;
- projects.items.forEach(project => {
- const boxArea = this.computeBBoxArea(project);
- project.districts &&
- project.districts.features.forEach(districtFeature => {
- // Some very small holes may collapse to a single point during the merge operation,
- // and generate invalid polygons that cause simplify to fail
- //eslint-disable-next-line functional/immutable-data
- districtFeature.geometry.coordinates = districtFeature.geometry.coordinates
- .map(polygonCoords =>
- polygonCoords.flatMap(ringCoords => {
- if (ringCoords.every(coord => _.isEqual(coord, ringCoords[0]))) {
- return [];
- }
- return [ringCoords];
- })
+ return (
+ this.repo
+ .createQueryBuilder("project")
+ .innerJoin("project.regionConfig", "regionConfig")
+ .innerJoin("project.user", "user")
+ .leftJoin("project.chamber", "chamber")
+ .select([
+ "project.id",
+ "project.name",
+ "project.numberOfDistricts",
+ "project.updatedDt",
+ "project.createdDt",
+ "project.districts",
+ "regionConfig.name",
+ "regionConfig.id",
+ "user.id",
+ "user.name"
+ ])
+ // Replace the districts column with a simplified one to save on response size
+ //
+ // Note that we're doing a bit of a trick here to replace the contents of the districts column,
+ // we need to select it above, and then give an alias here that will override that selection
+ .addSelect(
+ `CASE
+ WHEN districts IS NULL THEN NULL
+ ELSE JSON_BUILD_OBJECT(
+ 'type', 'FeatureCollection',
+ 'features', ARRAY(
+ SELECT JSON_BUILD_OBJECT(
+ 'type', 'Feature',
+ 'properties', feature->'properties',
+ 'geometry', ST_AsGeoJSON(ST_Simplify(ST_GeomFromGeoJSON(feature->'geometry'), 0.001))::json
+ )
+ FROM jsonb_array_elements(districts->'features') feature
+ )
)
- .filter(polygonCoords => polygonCoords.length > 0);
- try {
- simplify(districtFeature, { mutate: true, tolerance: boxArea > 1 ? 0.005 : 0.001 });
- } catch (e) {
- this.logger.debug(
- `Could not simplify district ${districtFeature.id} for project ${project.id}: ${e}`
- );
- }
- });
- });
- return projects;
+ END`,
+ "project_districts"
+ )
+ .orderBy("project.updatedDt", "DESC")
+ );
}
async findAllPublishedProjectsPaginated(
@@ -110,7 +89,7 @@ export class ProjectsService extends TypeOrmCrudService {
? builderWithFilter.andWhere("regionConfig.regionCode = :region", { region: options.region })
: builderWithFilter;
- return this.simplifyDistricts(paginate(builderWithRegion, options));
+ return paginate(builderWithRegion, options);
}
async findAllUserProjectsPaginated(
@@ -122,6 +101,6 @@ export class ProjectsService extends TypeOrmCrudService {
{ userId }
);
- return this.simplifyDistricts(paginate(builder, options));
+ return paginate(builder, options);
}
}
diff --git a/src/server/yarn.lock b/src/server/yarn.lock
index 3fad30de3..c9f6adaf3 100644
--- a/src/server/yarn.lock
+++ b/src/server/yarn.lock
@@ -580,21 +580,6 @@
"@turf/helpers" "^6.5.0"
"@turf/meta" "^6.5.0"
-"@turf/clean-coords@^6.5.0":
- version "6.5.0"
- resolved "https://registry.yarnpkg.com/@turf/clean-coords/-/clean-coords-6.5.0.tgz#6690adf764ec4b649710a8a20dab7005efbea53f"
- integrity sha512-EMX7gyZz0WTH/ET7xV8MyrExywfm9qUi0/MY89yNffzGIEHuFfqwhcCqZ8O00rZIPZHUTxpmsxQSTfzJJA1CPw==
- dependencies:
- "@turf/helpers" "^6.5.0"
- "@turf/invariant" "^6.5.0"
-
-"@turf/clone@^6.5.0":
- version "6.5.0"
- resolved "https://registry.yarnpkg.com/@turf/clone/-/clone-6.5.0.tgz#895860573881ae10a02dfff95f274388b1cda51a"
- integrity sha512-mzVtTFj/QycXOn6ig+annKrM6ZlimreKYz6f/GSERytOpgzodbQyOgkfwru100O1KQhhjSudKK4DsQ0oyi9cTw==
- dependencies:
- "@turf/helpers" "^6.5.0"
-
"@turf/distance@6.x":
version "6.0.1"
resolved "https://registry.yarnpkg.com/@turf/distance/-/distance-6.0.1.tgz#0761f28784286e7865a427c4e7e3593569c2dea8"
@@ -620,13 +605,6 @@
dependencies:
"@turf/helpers" "6.x"
-"@turf/invariant@^6.5.0":
- version "6.5.0"
- resolved "https://registry.yarnpkg.com/@turf/invariant/-/invariant-6.5.0.tgz#970afc988023e39c7ccab2341bd06979ddc7463f"
- integrity sha512-Wv8PRNCtPD31UVbdJE/KVAWKe7l6US+lJItRR/HOEW3eh+U/JwRCSUl/KZ7bmjM/C+zLNoreM2TU6OoLACs4eg==
- dependencies:
- "@turf/helpers" "^6.5.0"
-
"@turf/length@6.0.2":
version "6.0.2"
resolved "https://registry.yarnpkg.com/@turf/length/-/length-6.0.2.tgz#22d91a6d0174e862a3614865613f1aceb1162dac"
@@ -658,16 +636,6 @@
"@turf/helpers" "6.x"
"@turf/invariant" "6.x"
-"@turf/simplify@^6.5.0":
- version "6.5.0"
- resolved "https://registry.yarnpkg.com/@turf/simplify/-/simplify-6.5.0.tgz#ec435460bde0985b781618b05d97146c32c8bc16"
- integrity sha512-USas3QqffPHUY184dwQdP8qsvcVH/PWBYdXY5am7YTBACaQOMAlf6AKJs9FT8jiO6fQpxfgxuEtwmox+pBtlOg==
- dependencies:
- "@turf/clean-coords" "^6.5.0"
- "@turf/clone" "^6.5.0"
- "@turf/helpers" "^6.5.0"
- "@turf/meta" "^6.5.0"
-
"@types/anymatch@*":
version "1.3.1"
resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a"