Skip to content

Commit

Permalink
Use tagged types for DB Geometry types
Browse files Browse the repository at this point in the history
  • Loading branch information
arttuka committed Jan 4, 2025
1 parent de3f907 commit 0612627
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 36 deletions.
4 changes: 2 additions & 2 deletions resources/sql/init-db.sql
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ LANGUAGE plpgsql
IMMUTABLE
RETURNS NULL ON NULL INPUT;

CREATE OR REPLACE FUNCTION AsJSON(g geometry) RETURNS text
CREATE OR REPLACE FUNCTION AsJSON(g geometry) RETURNS jsonb
AS $$
SELECT ST_AsGeoJSON(ST_Transform(g, 4326), 6)
SELECT ST_AsGeoJSON(ST_Transform(g, 4326), 6)::jsonb
$$
LANGUAGE SQL
IMMUTABLE
Expand Down
20 changes: 8 additions & 12 deletions src/server/db/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ import config from '../config'
import { Lane, Route, RouteType, Waypoint } from '../../common/types'
import { Database, PgrDijkstra } from './types'
import {
aggBuilder,
arrayAgg,
asJSON,
distance,
extendKysely,
hydrate,
makeLine,
Expand Down Expand Up @@ -107,9 +108,7 @@ const insertEndpoints = async (
.as('point'),
])
.$call(whereNullOrGreater('depth', depth))
.orderBy(({ eb, ref }) =>
eb('lane.geom', '<->', ref('origin.geom'))
)
.orderBy((eb) => distance(eb, 'lane.geom', 'origin.geom'))
.limit(sql.lit(1))
.as('l')
)
Expand Down Expand Up @@ -153,14 +152,11 @@ const insertExtraLanes = async (tx: Transaction<Database>): Promise<void> => {
.with('lane_endpoints', (db) =>
db
.selectFrom('endpoint')
.select(({ fn }) => {
const arrayAgg = aggBuilder(fn, 'array_agg')
return [
'lane as laneid',
arrayAgg<number[]>(['vertex'], 'seq').as('vertex_ids'),
arrayAgg<string[]>(['point'], 'seq').as('points'),
]
})
.select(({ fn }) => [
'lane as laneid',
arrayAgg(fn, ['vertex'], 'seq').as('vertex_ids'),
arrayAgg(fn, ['point'], 'seq').as('points'),
])
.where('type', '!=', sql.lit('viadirect' as const))
.where('vertex', '<', sql.lit(0))
.groupBy(['lane'])
Expand Down
23 changes: 14 additions & 9 deletions src/server/db/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ import {
import { joinList, getSqlType } from './util'
import { WaypointType } from '../../common/types'

declare const tags: unique symbol
export type DbPoint = string & { [tags]: { Point: void } }
export type DbLineString = string & { [tags]: { LineString: void } }
export type DbGeometry = DbPoint | DbLineString

export type Database = {
lane: LaneTable
lane_vertices_pgr: LaneVerticesPgrTable
Expand All @@ -28,7 +33,7 @@ export type BaseLaneTable = {
length: number
depth: number
height: number
geom: string
geom: DbLineString
}

export type LaneTable = BaseLaneTable & {
Expand All @@ -40,7 +45,7 @@ export type LaneTable = BaseLaneTable & {

export type LaneVerticesPgrTable = {
id: Generated<number>
the_geom: string
the_geom: DbPoint
}

export type ExtraLaneTable = BaseLaneTable & {
Expand All @@ -51,19 +56,19 @@ export type EndpointTable = {
seq: number
lane: number
vertex: number
geometry: string
point: string
geometry: DbLineString
point: DbPoint
type: WaypointType
}

export type SegmentView = {
seq: number
source: number
target: number
source_point: string
target_point: string
source_geometry: string
target_geometry: string
source_point: DbPoint
target_point: DbPoint
source_geometry: DbLineString
target_geometry: DbLineString
direct: boolean
type: WaypointType
}
Expand All @@ -73,7 +78,7 @@ export type SplitLinestring = {
source: number
target: number
length: number
geom: string
geom: DbLineString
}

export type PgrDijkstra = {
Expand Down
56 changes: 43 additions & 13 deletions src/server/db/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
Expression,
ExpressionBuilder,
ExtractTypeFromReferenceExpression,
ExtractTypeFromStringReference,
FunctionModule,
Kysely,
SelectQueryBuilder,
Expand All @@ -16,9 +17,13 @@ import {
} from 'kysely'
import { LineString } from 'geojson'
import {
DbGeometry,
DbLineString,
DbPoint,
PgrDijkstra,
SplitLinestring,
TypedReferenceExpression,
TypedStringReference,
ValuesExpression,
} from './types'
import { NotEmptyArray } from '../../common/types'
Expand Down Expand Up @@ -69,17 +74,36 @@ export const aggBuilder =
dir: 'asc' | 'desc' = 'asc'
) => {
const [firstArgs, lastArg] = splitLast(args)
return fn.agg<O, RE>(name, [
return fn.agg<O>(name, [
...firstArgs,
sql`${parseAggExpression(lastArg)} ORDER BY ${parseAggExpression(orderBy)} ${sql.raw(dir)}` as unknown as RE,
sql`${parseAggExpression(lastArg)} ORDER BY ${parseAggExpression(orderBy)} ${sql.raw(dir)}`,
])
}

type ExtractType<Db, Tb extends keyof Db, E extends AggExpression<Db, Tb>> =
E extends StringReference<Db, Tb>
? ExtractTypeFromStringReference<Db, Tb, E>
: E extends Expression<infer T>
? T
: never

export const arrayAgg = <
Db,
Tb extends keyof Db,
RE extends AggExpression<Db, Tb> = AggExpression<Db, Tb>,
OE extends AggExpression<Db, Tb> = AggExpression<Db, Tb>,
>(
fn: FunctionModule<Db, Tb>,
args: Readonly<NotEmptyArray<RE>>,
orderBy: OE,
dir: 'asc' | 'desc' = 'asc'
) => aggBuilder(fn, 'array_agg')<ExtractType<Db, Tb, RE>[]>(args, orderBy, dir)

export const splitLinestring = <Db, Tb extends keyof Db>(
eb: ExpressionBuilder<Db, Tb>,
vertexIds: TypedReferenceExpression<Db, Tb, number[]>,
points: TypedReferenceExpression<Db, Tb, string[]>,
geom: TypedReferenceExpression<Db, Tb, string>,
points: TypedReferenceExpression<Db, Tb, DbPoint[]>,
geom: TypedReferenceExpression<Db, Tb, DbLineString>,
source: TypedReferenceExpression<Db, Tb, number>,
target: TypedReferenceExpression<Db, Tb, number>,
length: TypedReferenceExpression<Db, Tb, number>
Expand Down Expand Up @@ -111,6 +135,12 @@ export const hydrate = <T extends Compilable>(qb: T) => {
})
}

export const distance = <Db, Tb extends keyof Db>(
eb: ExpressionBuilder<Db, Tb>,
from: TypedStringReference<Db, Tb, DbGeometry>,
to: TypedStringReference<Db, Tb, DbGeometry>
) => eb(from, '<->', eb.ref(to)).$castTo<number>()

export const logQuery = <T extends Compilable>(qb: T): T => {
console.log(qb.compile())
return qb
Expand All @@ -123,26 +153,26 @@ export const logHydratedQuery = <T extends Compilable>(qb: T): T => {

export const asJSON = <Db, Tb extends keyof Db>(
eb: ExpressionBuilder<Db, Tb>,
expr: TypedReferenceExpression<Db, Tb, string>
) => eb.cast<LineString>(eb.fn('AsJSON', [expr]), 'jsonb')
expr: TypedReferenceExpression<Db, Tb, DbLineString>
) => eb.fn<LineString>('AsJSON', [expr])

export const makeLine = <Db, Tb extends keyof Db>(
export const makeLine = <Db, Tb extends keyof Db, T extends DbGeometry>(
eb: ExpressionBuilder<Db, Tb>,
...exprs: TypedReferenceExpression<Db, Tb, string>[]
) => asJSON(eb, eb.fn('ST_MakeLine', exprs))
...exprs: TypedReferenceExpression<Db, Tb, T>[]
) => asJSON(eb, eb.fn<DbLineString>('ST_MakeLine', exprs))

export const makeLineAgg = <Db, Tb extends keyof Db>(
export const makeLineAgg = <Db, Tb extends keyof Db, T extends DbGeometry>(
eb: ExpressionBuilder<Db, Tb>,
expr: TypedReferenceExpression<Db, Tb, string>,
expr: TypedReferenceExpression<Db, Tb, T>,
orderBy: AggExpression<Db, Tb>
) => asJSON(eb, aggBuilder(eb.fn, 'ST_MakeLine')([expr], orderBy))
) => asJSON(eb, aggBuilder(eb.fn, 'ST_MakeLine')<DbLineString>([expr], orderBy))

export const makePoint = <Db, Tb extends keyof Db>(
eb: ExpressionBuilder<Db, Tb>,
lng: TypedReferenceExpression<Db, Tb, number>,
lat: TypedReferenceExpression<Db, Tb, number>
) =>
eb.fn<string>('ST_Transform', [
eb.fn<DbPoint>('ST_Transform', [
eb.fn('ST_SetSRID', [eb.fn('ST_MakePoint', [lng, lat]), eb.lit(4326)]),
eb.lit(3067),
])
Expand Down

0 comments on commit 0612627

Please sign in to comment.