Skip to content
This repository has been archived by the owner on May 4, 2024. It is now read-only.

Commit

Permalink
Show Roads
Browse files Browse the repository at this point in the history
- Road forming
- Update Road component
  • Loading branch information
Seb-sti1 committed Nov 9, 2023
1 parent 93cab98 commit 1a6fc72
Show file tree
Hide file tree
Showing 7 changed files with 220 additions and 147 deletions.
6 changes: 3 additions & 3 deletions backend/src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export interface SurveyConditions {
/**
* The OSM id of a way.
*/
export type WayId = string;
export type OSMWayId = string;

/**
* The description of a road
Expand All @@ -51,8 +51,8 @@ export type WayId = string;
*/
export interface Road {
way_name: string;
way_ids: WayId[][];
geometries: Record<WayId, LatLng[]>;
branches: OSMWayId[][];
geometries: Record<OSMWayId, LatLng[]>;
}

/**
Expand Down
10 changes: 8 additions & 2 deletions backend/src/roads/road.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,22 @@ import { RoadService } from './road.service';
export class RoadController {
constructor(private readonly service: RoadService) {}

/**
* Get all the roads in the database.
*
* @author Kerbourc'h
*/
@Get('')
getRoads() {
return this.service.getSurveysPath();
async getRoads() {
return await this.service.getRoads();
}

/**
* Add a specific road to the database using the OSM id of a way in the road.
* **It might get removed in the future.**
*
* @param id the OSM id of a way in the road
* @author Kerbourc'h
*/
@Get('getorcreateway/:id')
async insertRoad(@Param('id') id: string) {
Expand Down
199 changes: 83 additions & 116 deletions backend/src/roads/road.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { InjectConnection, Knex } from 'nestjs-knex';
import { IWay, Way } from '../tables';
import { LatLng, OSMWayId, Road } from '../models';
import { getOSMWaysInARoad } from './osm';
import { constructNavigationTable, findLongestBranch } from './roads';

@Injectable()
export class RoadService {
Expand Down Expand Up @@ -134,43 +135,23 @@ export class RoadService {

/**
* Query the geometry, node_start and node_end of each way and structure the data properly
*
* @author Kerbourc'h
*/
async getWaysGeometry() {
const ways: Record<
WayId,
{
geometry: LatLng[];
node_start: number;
node_end: number;
}
> = Object.fromEntries(
(
await Ways(this.knex_liramap).select(
this.knex_liramap.raw('"OSM_Id"'),
this.knex_liramap.raw(
"ST_AsGeoJSON(section_geom)::json->'coordinates' as geometry",
),
'node_start',
'node_end',
)
).map((way) => [
way.OSM_Id,
{
geometry: way.geometry
? way.geometry.map((coord: number[]) => {
return {
lat: coord[1],
lng: coord[0],
};
})
: null,
node_start: way.node_start,
node_end: way.node_end,
},
]),
);

return ways;
async getWaysGeometry(OSMWayId: OSMWayId[]): Promise<IWay[]> {
return Way(this.knex_groupd)
.select(
'id',
'way_name',
'osm_id',
'node_start',
'node_end',
'length',
this.knex_groupd.raw(
'ST_AsGeoJSON(section_geom)::json as section_geom',
),
)
.whereIn('osm_id', OSMWayId);
}

/**
Expand All @@ -179,123 +160,109 @@ export class RoadService {
* TODO: create a function of the following and add unit tests with mock data:
* // - a road completely split into two branches
* // - a road splitting into two branches and then merging back
*
* @author Kerbourc'h
*/
roadsForming(ways: any, waysInRoads: any) {
const roads: Road[] = [];
async roadsForming(wayIds: OSMWayId[], wayName: string): Promise<Road> {
// Extract the way object
let ways: Record<OSMWayId, IWay<LatLng[]>> = {};
for (const way of await this.getWaysGeometry(wayIds)) {
ways[way.osm_id] = {
...way,
section_geom: way.section_geom.coordinates.map(
(coordinate: number[]): LatLng => {
return {
lat: coordinate[1],
lng: coordinate[0],
};
},
),
};
}

for (const road of waysInRoads) {
if (road.way_ids.length === 0) continue;
// Create record storing for each way next and previous ways
let navigationTable: Record<
OSMWayId,
{ next: OSMWayId[]; prev: OSMWayId[] }
> = constructNavigationTable(ways);

let geometries: Record<WayId, LatLng[]> = {};
const longestBranches: { ids: OSMWayId[]; length: number }[] = [];

// extract the geometry of each way
for (const way_id of road.way_ids) {
geometries[way_id] = ways[way_id].geometry;
// add all the longest branch of all the way with no prev way
for (const way_id of Object.keys(navigationTable)) {
if (navigationTable[way_id].prev.length === 0) {
longestBranches.push(findLongestBranch(way_id, navigationTable, ways));
}
}

let road_way_ids: WayId[][] = [];

let road_branch_idx = 0;
while (road.way_ids.length > 0) {
// extract branches of the road
let curr_way_id = road.way_ids[0];
// find the beginning of the branch containing road
for (const way_id of road.way_ids) {
if (ways[way_id].node_end === ways[curr_way_id].node_start) {
curr_way_id = way_id;
}
}

// extract the branch
road_way_ids[road_branch_idx] = [];
while (curr_way_id) {
road_way_ids[road_branch_idx].push(curr_way_id);

// find the next way (or undefined if the branch is finished)
const next_way_id: WayId = road.way_ids.find(
(way_id: WayId) =>
ways[curr_way_id].node_end === ways[way_id].node_start,
);
// remove the way from the list
road.way_ids.splice(road.way_ids.indexOf(curr_way_id), 1);
// For each look if the way is in other branch and remove
// the way from the shortest branch
for (let i = 0; i < longestBranches.length; i++) {
const branch = longestBranches[i];
for (let j = 0; j < i; j++) {
const otherBranch = longestBranches[j];

curr_way_id = next_way_id;
// remove the way in common to the two branches in the shortest branch
if (branch.length > otherBranch.length) {
otherBranch.ids = otherBranch.ids.filter((id) => {
if (branch.ids.includes(id)) otherBranch.length -= ways[id].length;
return !branch.ids.includes(id);
});
} else {
branch.ids = branch.ids.filter((id) => {
if (otherBranch.ids.includes(id)) branch.length -= ways[id].length;
return !otherBranch.ids.includes(id);
});
}
road_branch_idx++;
}

roads.push({
way_name: road.way_name,
way_ids: road_way_ids,
geometries: geometries,
});
}

return roads;
}

/**
* Return the path of the surveys.
*/
async getSurveysPath() {
const ways = await this.getWaysGeometry();
const branches: OSMWayId[][] = longestBranches.map((branch) => branch.ids);
// TODO expends branches in the opposite direction if the way is not isoneway

// SELECT ARRAY_AGG(DISTINCT "OSM_Id"), fk_trip_id
// FROM coverage
// JOIN ways ON coverage.fk_way_id = ways.id
// GROUP BY fk_trip_id
const waysInRoads = (
await Ways(this.knex_liramap)
.select(
this.knex_liramap.raw('ARRAY_AGG(DISTINCT "OSM_Id") as way_ids'),
'fk_trip_id',
)
.join('coverage', 'coverage.fk_way_id', 'ways.id')
.groupBy('fk_trip_id')
).map((road: { way_ids: string[]; fk_trip_id: string }) => {
return {
way_ids: road.way_ids,
way_name: road.fk_trip_id,
};
const geometries: Record<OSMWayId, LatLng[]> = {};
Object.values(ways).forEach((way) => {
geometries[way.osm_id] = way.section_geom;
});

return this.roadsForming(ways, waysInRoads);
return { way_name: wayName, branches: branches, geometries: geometries };
}

/**
* Return a dictionary of the roads. A road is defined by a name, a list of branch (each branch
* is a list of way ids) and the geometry (a list of coordinates) of each way.
*
* TODO: return branch corresponding to the direction of the road instead of chunk of road
* TODO: add parameters to get the road of a specific area
* TODO: grouping the way by road name is not perfect, some roads can have the same name...
* TODO: add parameters to get the road of a specific area (then remove the limit in the query)
*
* @author Kerbourc'h
*/
async getRoads() {
const ways = await this.getWaysGeometry();

// Query the way ids of a road ordered by the number of ways in the road. Also remove the way_count column.
const waysInRoads = (
await Ways(this.knex_liramap)
await Way(this.knex_groupd)
.select(
this.knex_liramap.raw('ARRAY_AGG("OSM_Id") as way_ids'),
this.knex_groupd.raw('ARRAY_AGG("osm_id") as way_ids'),
'way_name',
this.knex_liramap.raw(
'ARRAY_LENGTH(ARRAY_AGG("OSM_Id"), 1) as way_count',
this.knex_groupd.raw(
'ARRAY_LENGTH(ARRAY_AGG("osm_id"), 1) as way_count',
),
)
.where('way_name', '!=', '')
.groupBy('way_name')
// This is a small trick to show the bigger roads first
.orderBy('way_count', 'desc')
// TODO: remove limit when frontend can deals with it
.limit(700)
).map((road) => {
.limit(500)
).map((road): { way_ids: string[]; way_name: string } => {
return {
way_ids: road.way_ids,
way_name: road.way_name,
};
});

return this.roadsForming(ways, waysInRoads);
return Promise.all(
waysInRoads.map(async (waysInRoad) =>
this.roadsForming(waysInRoad.way_ids, waysInRoad.way_name),
),
);
}
}
Loading

0 comments on commit 1a6fc72

Please sign in to comment.