Skip to content

Commit

Permalink
feat: #632 Apply spatial simplify algorithm for FOM geometry (#648)
Browse files Browse the repository at this point in the history
* feat(632):
- add migration script to simplify existing geomtry
- simplify geometry on submission
refs: #632

* feat(632): add wording in spatial submission screen for explaining the simplication algorithm, refs: #632

* fix(632): fix wording for consistancy, refs:#632

* fix(632): add style for expanded section, refs: #632

* fix(632): apply simplication algorithm before saving the submission, refs: #632

* fix(632): add comment for the 2.5m tolerance, refs: #632

* Revert back 'simplifyGeometry' as update statement.

---------

Co-authored-by: Ian Liu <[email protected]>
  • Loading branch information
MCatherine1994 and ianliuwk1019 authored Jun 24, 2024
1 parent 78b6ada commit c5aca4f
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@

<mat-expansion-panel hideToggle>
<mat-expansion-panel class="expand-section">
<mat-expansion-panel-header>
<mat-panel-title>
<strong>FOM Geospatial Submission Format Overview</strong>
</mat-panel-title>
<mat-panel-description>
Click to expand/collapse
</mat-panel-description>
</mat-expansion-panel-header>

<p>
Expand Down Expand Up @@ -261,3 +258,19 @@
</section>
</mat-expansion-panel>



<mat-expansion-panel class="expand-section">
<mat-expansion-panel-header>
<mat-panel-title>
<strong>How Your Data Is Handled</strong>
</mat-panel-title>
</mat-expansion-panel-header>

<p>
The Forest Operations Map application simplifies detailed maps that users submit.
To save space and speed up processing, the application reduces the number of points in these maps.
It keeps the map's original shape accurate within about 2.5 meters.
This process uses the Douglas-Peucker algorithm.
</p>
</mat-expansion-panel>
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,9 @@ ol {
li {
margin-bottom: 0.5em;
}
}

.expand-section {
margin-bottom: 16px;
padding: 2px;
}
9 changes: 6 additions & 3 deletions api/src/app/modules/submission/cut-block.entity.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { ApiBaseEntity } from '@entities';
import {
Column,
Entity,
PrimaryGeneratedColumn,
JoinColumn,
Column,
ManyToOne,
PrimaryGeneratedColumn,
RelationId
} from 'typeorm';
import { Submission } from './submission.entity';

@Entity('cut_block', { schema: 'app_fom' })
@Entity(CutBlock.tableName, { schema: 'app_fom' })
export class CutBlock extends ApiBaseEntity<CutBlock> {

static readonly tableName = 'cut_block';

constructor(cutBlock?: Partial<CutBlock>) {
super(cutBlock);
}
Expand Down
7 changes: 5 additions & 2 deletions api/src/app/modules/submission/retention-area.entity.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { ApiBaseEntity } from '@entities';
import { Entity, PrimaryGeneratedColumn, JoinColumn, Column, ManyToOne, RelationId } from 'typeorm';
import { Column, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn, RelationId } from 'typeorm';
import { Submission } from './submission.entity';

@Entity('retention_area', {schema: 'app_fom'})
@Entity(RetentionArea.tableName, {schema: 'app_fom'})
export class RetentionArea extends ApiBaseEntity<RetentionArea> {

static readonly tableName = 'retention_area';

constructor(retentionArea?: Partial<RetentionArea>) {
super(retentionArea);
}
Expand Down
9 changes: 6 additions & 3 deletions api/src/app/modules/submission/road-section.entity.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { ApiBaseEntity } from '@entities';
import {
Column,
Entity,
PrimaryGeneratedColumn,
JoinColumn,
Column,
ManyToOne,
PrimaryGeneratedColumn,
RelationId
} from 'typeorm';
import { Submission } from './submission.entity';

@Entity('road_section', { schema: 'app_fom' })
@Entity(RoadSection.tableName, { schema: 'app_fom' })
export class RoadSection extends ApiBaseEntity<RoadSection> {

static readonly tableName = 'road_section';

constructor(roadSection?: Partial<RoadSection>) {
super(roadSection);
}
Expand Down
66 changes: 60 additions & 6 deletions api/src/app/modules/submission/submission.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,10 @@ export class SubmissionService extends DataService<Submission, Repository<Submis
submission.retentionAreas = <RetentionArea[]>spatialObjects;
}

await this.saveAndUpdateSpatialSubmission(submission, dto.spatialObjectCode, user);
await this.repository.save(submission);
// Apply Douglas-Peucker algorithm to simplify geometry.
await this.simplifyGeometry(submission.id, dto.spatialObjectCode);
await this.updateProjectLocationAndSpatialAreaOrLength(submission, dto.spatialObjectCode, user);
}

/**
Expand Down Expand Up @@ -493,7 +496,7 @@ export class SubmissionService extends DataService<Submission, Repository<Submis
* @param srid The EPSG code used as spatial reference identifier (SRID) with a specific coordinate system
*/
async convertGeometry(geometry: string, srid: number): Promise<string> {
this.logger.debug(`Coverting geometry to EPSG${srid} with geometry: ${geometry}`)
this.logger.debug(`Converting geometry to EPSG${srid} with geometry: ${geometry}`)
try {
const convertedGeometryResult = await this.getDataSource()
.query(
Expand Down Expand Up @@ -664,15 +667,66 @@ export class SubmissionService extends DataService<Submission, Repository<Submis
await this.updateProjectLocation(submission.projectId, user); // This will set geometry_latlong to null.
}
else {
await this.saveAndUpdateSpatialSubmission(submission, spatialObjectCode, user);
await this.repository.save(submission);
await this.updateProjectLocationAndSpatialAreaOrLength(submission, spatialObjectCode, user)
}
}

private async saveAndUpdateSpatialSubmission(
private getSpatialTableNameBySpatialObjectCode(spatialObjectCode: SpatialObjectCodeEnum) {
let spatialTableName: string;
switch (spatialObjectCode) {
case SpatialObjectCodeEnum.CUT_BLOCK:
spatialTableName = CutBlock.tableName
break;
case SpatialObjectCodeEnum.ROAD_SECTION:
spatialTableName = RoadSection.tableName;
break;
case SpatialObjectCodeEnum.WTRA:
spatialTableName = RetentionArea.tableName;
break;
default:
throw new BadRequestException("Unrecognized spatial object code.");
}
return spatialTableName;
}

/**
* Simplify the geometry using Douglas-Peucker algorithm with a tolerance of 2.5m,
* which is established by Forestry Geospatial experts as the standard accuracy level for forestry applications.
* @param submissionID the id for the FOM spatial submission.
* @param spatialObjectCode the GeoSpatial Object Type for this spatial submission.
* @returns no return. Update and simplify the 'geometry' on particular spatial object type table.
*/
private async simplifyGeometry(submissionID: number, spatialObjectCode: SpatialObjectCodeEnum) {
this.logger.debug(`Simplify geometry for submittion: ${submissionID}, spatial object type: ${spatialObjectCode}`);

const buildUpdateQuery = (submissionID: number, spatialObjectCode: SpatialObjectCodeEnum) => {
let targetSpatialTable = this.getSpatialTableNameBySpatialObjectCode(spatialObjectCode);
return `
UPDATE app_fom.${targetSpatialTable} SET geometry=ST_SimplifyPreserveTopology(geometry, 2.5) where submission_id = ${submissionID};
`
}

try {
await this.getDataSource()
.query(
buildUpdateQuery(submissionID, spatialObjectCode)
);
this.logger.debug(`Simplify geometry successfully`);
}
catch (error) {
throw new BadRequestException(`Failed on simplifying geometry for submittion: ${submissionID}, spatial object type: ${spatialObjectCode}: ${error}`);
}
}

// Whenever user updates FOM submission again on particular spatial type,
// the project location needs to be recalculated
// and that spatial area/length needs to be updated.
private async updateProjectLocationAndSpatialAreaOrLength(
updatedSubmission: Submission,
spatialObjectCode: SpatialObjectCodeEnum,
user: User) {
await this.repository.save(updatedSubmission);
user: User
) {
await this.updateProjectLocation(updatedSubmission.projectId, user);
await this.updateGeospatialAreaOrLength(spatialObjectCode, updatedSubmission.id);
}
Expand Down
26 changes: 26 additions & 0 deletions api/src/migrations/main/1718643595423-simplify-spatial-geometry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const { MigrationInterface, QueryRunner } = require('typeorm');

module.exports = class SimplifySpatialGeometry1718643595423 {
async up(queryRunner) {
console.log(
'Starting geometry simplification (update geometry column in cut_block, road_section and retention_area table) migration'
);
await queryRunner.query(`
-- update geometry column to apply the simplification algorithm in cut_block table
UPDATE app_fom.cut_block SET geometry=ST_SimplifyPreserveTopology(geometry, 2.5);
-- update geometry column to apply the simplification algorithm in retention_area table
UPDATE app_fom.retention_area SET geometry=ST_SimplifyPreserveTopology(geometry, 2.5);
-- update geometry column to apply the simplification algorithm in road_section table
UPDATE app_fom.road_section SET geometry=ST_SimplifyPreserveTopology(geometry, 2.5);
`);
}

async down(queryRunner) {
console.log(
'ERROR: Apply simplification algorithm on geometry cannot be reversed.'
);
}
};

0 comments on commit c5aca4f

Please sign in to comment.