Skip to content

Commit

Permalink
Merge pull request #343 from dfpc-coe/server-layer
Browse files Browse the repository at this point in the history
Server Overlays
  • Loading branch information
ingalls authored Sep 21, 2024
2 parents 3778e92 + 1dc7480 commit 7e7ca5d
Show file tree
Hide file tree
Showing 33 changed files with 8,663 additions and 1,069 deletions.
119 changes: 119 additions & 0 deletions api/lib/control/tilejson.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import undici from 'undici';
import type { BBox } from 'geojson';
import type { Response } from 'express';
import { pointOnFeature } from '@turf/point-on-feature';
import { bboxPolygon } from '@turf/bbox-polygon';
import { Static, Type } from '@sinclair/typebox'
import Err from '@openaddresses/batch-error';
import { validateStyleMin } from '@maplibre/maplibre-gl-style-spec';

export const TileJSONType = Type.Object({
tilejson: Type.String(),
version: Type.String(),
name: Type.String(),
minzoom: Type.Integer(),
maxzoom: Type.Integer(),
tiles: Type.Array(Type.String()),
bounds: Type.Array(Type.Number()),
center: Type.Array(Type.Number()),
type: Type.String(),
layers: Type.Array(Type.Unknown()),
format: Type.Optional(Type.String()),
})

export interface TileJSONInterface {
name: string;
url: string;
bounds?: Array<number>;
center?: Array<number>;
type?: string;
version?: string;
minzoom?: number;
maxzoom?: number;
}

export default class TileJSON {
static isValidStyle(type: string, layers: Array<any>): void {
const sources: Record<string, unknown> = {};

for (const l of layers) {
if (!sources[l.source]) {
sources[l.source] = { type }
}
}

const errors = validateStyleMin({
version: 8,
glyphs: '/fonts/{fontstack}/{range}.pbf',
sources: sources as any,
layers: layers as any
})

if (errors.length) throw new Err(400, null, JSON.stringify(errors));
}

static json(config: TileJSONInterface): Static<typeof TileJSONType> {
const bounds = config.bounds || [-180, -90, 180, 90];
const center = config.center || pointOnFeature(bboxPolygon(bounds as BBox)).geometry.coordinates;

return {
tilejson: "2.2.0",
version: config.version || '1.0.0',
name: config.name,
type: config.type || 'raster',
bounds, center,
minzoom: config.minzoom || 0,
maxzoom: config.maxzoom || 16,
tiles: [ String(config.url) ],
layers: []
}
}

static async tile(
config: TileJSONInterface,
z: number, x: number, y: number,
res: Response
): Promise<void> {
const url = new URL(config.url
.replace(/\{\$?z\}/, String(z))
.replace(/\{\$?x\}/, String(x))
.replace(/\{\$?y\}/, String(y))
);

const stream = await undici.pipeline(url, {
method: 'GET',
}, ({ statusCode, headers, body }) => {
if (headers) {
for (const key in headers) {
if (
![
'content-type',
'content-length',
'content-encoding',
'last-modified',
].includes(key)
) {
delete headers[key];
}
}
}

res.writeHead(statusCode, headers);

return body;
});

stream
.on('data', (buf) => {
res.write(buf)
})
.on('end', () => {
res.end()
})
.on('close', () => {
res.end()
})
.end()

}
}
3 changes: 2 additions & 1 deletion api/lib/control/video-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ export default class VideoServiceControl {

if (lease.proxy) {
try {
const proxy = new URL(opts.proxy);
const proxy = new URL(lease.proxy);

// Check for HLS Errors
if (['http:', 'https:'].includes(proxy.protocol)) {
Expand All @@ -315,6 +315,7 @@ export default class VideoServiceControl {
} catch (err) {
if (err instanceof Err) {
throw err;
// @ts-expect-error code is not defined in type
} else if (err instanceof TypeError && err.code === 'ERR_INVALID_URL') {
throw new Err(400, null, 'Invalid Video Stream URL');
} else {
Expand Down
11 changes: 8 additions & 3 deletions api/lib/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,10 +324,15 @@ export const Overlay = pgTable('overlays', {
name: text('name').notNull(),
created: timestamp('created', { withTimezone: true, mode: 'string' }).notNull().default(sql`Now()`),
updated: timestamp('updated', { withTimezone: true, mode: 'string' }).notNull().default(sql`Now()`),
minzoom: integer('minzoom').notNull().default(0),
maxzoom: integer('maxzoom').notNull().default(16),
format: text('format').notNull().default('png'),
type: text('type').notNull().default('vector'),
styles: json('styles'),
styles: json('styles').$type<Array<unknown>>().notNull().default([]),
url: text('url').notNull()
});
}, (t) => ({
unq: unique().on(t.name)
}));

export const ProfileOverlay = pgTable('profile_overlays', {
id: serial('id').primaryKey(),
Expand All @@ -340,7 +345,7 @@ export const ProfileOverlay = pgTable('profile_overlays', {
opacity: numeric('opacity').notNull().default('1'),
visible: boolean('visible').notNull().default(true),
token: text('token'),
styles: json('styles'),
styles: json('styles').$type<Array<unknown>>().notNull().default([]),
mode: text('mode').notNull(),
mode_id: text('mode_id'), // Used for Data not for Profile
url: text('url').notNull()
Expand Down
18 changes: 9 additions & 9 deletions api/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,15 @@ export const VideoLeaseResponse = createSelectSchema(schemas.VideoLease, {

export const OverlayResponse = createSelectSchema(schemas.Overlay, {
id: Type.Integer(),
styles: Type.Unknown(),
styles: Type.Array(Type.Unknown())
});

export const ProfileOverlayResponse = createSelectSchema(schemas.ProfileOverlay, {
id: Type.Integer(),
pos: Type.Integer(),
opacity: Type.Number(),
visible: Type.Boolean(),
styles: Type.Array(Type.Unknown())
});

export const LayerTemplateResponse = createSelectSchema(schemas.LayerTemplate, {
Expand All @@ -90,14 +98,6 @@ export const ProfileFeature = Type.Composite([ Feature.Feature, Type.Object({
path: Type.String({ default: '/' }),
})]);

export const ProfileOverlayResponse = createSelectSchema(schemas.ProfileOverlay, {
id: Type.Integer(),
pos: Type.Integer(),
opacity: Type.Number(),
visible: Type.Boolean(),
styles: Type.Unknown()
});

export const LayerAlertResponse = createSelectSchema(schemas.LayerAlert, {
id: Type.Integer(),
hidden: Type.Boolean(),
Expand Down
6 changes: 6 additions & 0 deletions api/migrations/0062_serious_xavin.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
ALTER TABLE "overlays" ADD COLUMN "minzoom" integer DEFAULT 0 NOT NULL;--> statement-breakpoint
ALTER TABLE "overlays" ADD COLUMN "maxzoom" integer DEFAULT 16 NOT NULL;--> statement-breakpoint
ALTER TABLE "overlays" ADD COLUMN "format" text DEFAULT 'png' NOT NULL;--> statement-breakpoint

DELETE FROM overlays;
ALTER TABLE "overlays" ADD CONSTRAINT "overlays_name_unique" UNIQUE("name");
8 changes: 8 additions & 0 deletions api/migrations/0063_glamorous_captain_midlands.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
UPDATE profile_overlays
SET styles = '[]'::JSON
WHERE
styles IS NULL
OR styles::TEXT = '"\"\""';

ALTER TABLE "profile_overlays" ALTER COLUMN "styles" SET DEFAULT '[]'::json;--> statement-breakpoint
ALTER TABLE "profile_overlays" ALTER COLUMN "styles" SET NOT NULL;
2 changes: 2 additions & 0 deletions api/migrations/0064_curved_hulk.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE "overlays" ALTER COLUMN "styles" SET DEFAULT '[]'::json;--> statement-breakpoint
ALTER TABLE "overlays" ALTER COLUMN "styles" SET NOT NULL;
Loading

0 comments on commit 7e7ca5d

Please sign in to comment.