Skip to content

Commit d9ab411

Browse files
committed
Refactor
1 parent 2f3f9cc commit d9ab411

8 files changed

Lines changed: 239 additions & 352 deletions

File tree

src/plugins/aequilibrae/AequilibraEReader.vue

Lines changed: 31 additions & 352 deletions
Large diffs are not rendered by default.

src/plugins/aequilibrae/db.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
export async function getTableNames(db: any): Promise<string[]> {
2+
const result = await db.exec("SELECT name FROM sqlite_master WHERE type='table';").get.objs;
3+
return result.map((row: any) => row.name);
4+
}
5+
6+
export async function getTableSchema(db: any, tableName: string): Promise<{ name: string; type: string; nullable: boolean }[]> {
7+
const result = await db.exec(`PRAGMA table_info("${tableName}");`).get.objs;
8+
return result.map((row: any) => ({
9+
name: row.name,
10+
type: row.type,
11+
nullable: row.notnull === 0,
12+
}));
13+
}
14+
15+
export async function getRowCount(db: any, tableName: string): Promise<number> {
16+
const result = await db.exec(`SELECT COUNT(*) as count FROM "${tableName}";`).get.objs;
17+
return result.length > 0 ? result[0].count : 0;
18+
}
19+
20+
export async function fetchGeoJSONFeatures(db: any, table: { name: string; columns: any[] }, layerName: string, layerConfig: any) {
21+
const columnNames = table.columns
22+
.filter((c: any) => c.name.toLowerCase() !== 'geometry')
23+
.map((c: any) => `"${c.name}"`)
24+
.join(', ');
25+
26+
const query = `
27+
SELECT ${columnNames},
28+
AsGeoJSON(geometry) as geojson_geom,
29+
GeometryType(geometry) as geom_type
30+
FROM "${table.name}"
31+
WHERE geometry IS NOT NULL
32+
LIMIT 1000000;
33+
`;
34+
const rows = await db.exec(query).get.objs;
35+
const features: any[] = [];
36+
for (const row of rows) {
37+
if (!row.geojson_geom) continue;
38+
const properties: any = { _table: table.name, _layer: layerName, _layerConfig: layerConfig };
39+
for (const col of table.columns) {
40+
const key = col.name;
41+
if (key.toLowerCase() !== 'geometry' && key !== 'geojson_geom' && key !== 'geom_type') {
42+
properties[key] = row[key];
43+
}
44+
}
45+
features.push({ type: 'Feature', geometry: row.geojson_geom, properties });
46+
}
47+
return features;
48+
}

src/plugins/aequilibrae/i18n.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const i18n = {
2+
messages: {
3+
en: {},
4+
de: {},
5+
},
6+
};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.c-aequilibrae-viewer { position: absolute; top: 0; bottom: 0; left: 0; right: 0; background-color: var(--bgCardFrame); display: flex; flex-direction: column; }
2+
.view-toggle-bar { padding: 0.5rem; border-bottom: 1px solid var(--borderColor); background-color: var(--bgPanel); z-index: 10; }
3+
.viewer { flex: 1; overflow: auto; padding: 0.5rem 1rem; }
4+
.map-viewer { position: relative; flex: 1; width: 100%; height: 100%; }
5+
.loading { padding: 2rem; text-align: center; font-size: 1.2rem; color: var(--textFancy); }
6+
.database-content { padding: 0.5rem 0; }
7+
.database-content h3 { margin-bottom: 1rem; color: var(--textFancy); }
8+
.database-content h4 { margin-top: 1.5rem; margin-bottom: 0.5rem; color: var(--link); font-weight: bold; }
9+
.database-content p { margin: 0.25rem 0; color: var(--text); }
10+
.database-content .columns { margin-left: 1rem; font-size: 0.9rem; color: var(--textFancy); }
11+
.database-content .columns div { margin: 0.1rem 0; }

src/plugins/aequilibrae/styling.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
export function parseColor(colorString?: string): { r: number; g: number; b: number } {
2+
if (!colorString) return { r: 89, g: 161, b: 79 };
3+
if (colorString.startsWith('#')) {
4+
if (colorString.length === 4) {
5+
const r = colorString[1];
6+
const g = colorString[2];
7+
const b = colorString[3];
8+
return {
9+
r: parseInt(r + r, 16),
10+
g: parseInt(g + g, 16),
11+
b: parseInt(b + b, 16),
12+
};
13+
}
14+
const hex = colorString.slice(1);
15+
const r = parseInt(hex.slice(0, 2), 16);
16+
const g = parseInt(hex.slice(2, 4), 16);
17+
const b = parseInt(hex.slice(4, 6), 16);
18+
return { r, g, b };
19+
}
20+
return { r: 89, g: 161, b: 79 };
21+
}
22+
23+
export function buildStyleArrays(features: any[], defaultOpacity = 0.8) {
24+
const n = features.length;
25+
const fillColors = new Uint8ClampedArray(n * 4);
26+
const lineColors = new Uint8ClampedArray(n * 3);
27+
const lineWidths = new Float32Array(n);
28+
const pointRadii = new Float32Array(n);
29+
30+
features.forEach((feature, i) => {
31+
const cfg = feature?.properties?._layerConfig || {};
32+
const geomType = (feature?.geometry?.type || '').toLowerCase();
33+
34+
const fill = parseColor(cfg.fillColor || '#59a14f');
35+
fillColors[i * 4 + 0] = fill.r;
36+
fillColors[i * 4 + 1] = fill.g;
37+
fillColors[i * 4 + 2] = fill.b;
38+
fillColors[i * 4 + 3] = Math.round((cfg.opacity ?? defaultOpacity) * 255);
39+
40+
const stroke = parseColor(cfg.strokeColor || cfg.fillColor || '#4e79a7');
41+
lineColors[i * 3 + 0] = stroke.r;
42+
lineColors[i * 3 + 1] = stroke.g;
43+
lineColors[i * 3 + 2] = stroke.b;
44+
45+
if (cfg.strokeWidth !== undefined) lineWidths[i] = cfg.strokeWidth;
46+
else if (geomType.includes('polygon')) lineWidths[i] = 1;
47+
else if (geomType.includes('line')) lineWidths[i] = 3;
48+
else lineWidths[i] = 2;
49+
50+
pointRadii[i] = cfg.radius ?? 4;
51+
});
52+
53+
const featureFilter = new Float32Array(n).fill(1);
54+
return { fillColors, lineColors, lineWidths, pointRadii, featureFilter };
55+
}

src/plugins/aequilibrae/types.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
export type GeometryType = 'polygon' | 'line' | 'point';
2+
3+
export interface LayerConfig {
4+
table: string;
5+
type: GeometryType;
6+
fillColor?: string;
7+
strokeColor?: string;
8+
strokeWidth?: number;
9+
radius?: number;
10+
opacity?: number;
11+
zIndex?: number;
12+
}
13+
14+
export interface VizDetails {
15+
title: string;
16+
description: string;
17+
database: string;
18+
view: 'table' | 'map' | '';
19+
layers: { [key: string]: LayerConfig };
20+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import SPL from 'spl.js';
2+
import YAML from 'yaml';
3+
import type { VizDetails, LayerConfig } from './types';
4+
import { getTableNames, getTableSchema, getRowCount, fetchGeoJSONFeatures } from './db';
5+
6+
export async function initSql() {
7+
const spl = await SPL();
8+
return spl;
9+
}
10+
11+
export async function openDb(spl: any, arrayBuffer: ArrayBuffer) {
12+
return spl.db(arrayBuffer);
13+
}
14+
15+
export async function parseYamlConfig(yamlText: string, subfolder: string | null): Promise<VizDetails> {
16+
const config = YAML.parse(yamlText);
17+
const dbFile = config.database || config.file;
18+
if (!dbFile) throw new Error('No database field found in YAML config');
19+
const databasePath = dbFile.startsWith('/') ? dbFile : subfolder ? `${subfolder}/${dbFile}` : dbFile;
20+
return {
21+
title: config.title || dbFile,
22+
description: config.description || '',
23+
database: databasePath,
24+
view: config.view || '',
25+
layers: config.layers || {},
26+
};
27+
}
28+
29+
export async function buildTables(db: any, layerConfigs: { [k: string]: LayerConfig }, allNames?: string[]) {
30+
const names = allNames ?? (await getTableNames(db));
31+
const select = Object.keys(layerConfigs).length
32+
? [...new Set(Object.values(layerConfigs).map((c) => c.table))]
33+
: ['nodes', 'links', 'zones'];
34+
35+
const tables: Array<{ name: string; type: string; rowCount: number; columns: any[] }> = [];
36+
let hasGeometry = false;
37+
38+
for (const name of names) {
39+
if (!select.includes(name)) continue;
40+
const schema = await getTableSchema(db, name);
41+
const rowCount = await getRowCount(db, name);
42+
const hasGeomCol = schema.some((c: any) => c.name.toLowerCase() === 'geometry');
43+
if (hasGeomCol) hasGeometry = true;
44+
tables.push({ name, type: 'table', rowCount, columns: schema });
45+
}
46+
return { tables, hasGeometry };
47+
}
48+
49+
export async function buildGeoFeatures(db: any, tables: any[], layerConfigs: { [k: string]: LayerConfig }) {
50+
const plain = JSON.parse(JSON.stringify(layerConfigs));
51+
const layersToProcess = Object.keys(plain).length
52+
? Object.entries(plain)
53+
: tables
54+
.filter((t) => t.columns.some((c: any) => c.name.toLowerCase() === 'geometry'))
55+
.map((t) => [t.name, { table: t.name, type: 'line' as const }]);
56+
57+
const features: any[] = [];
58+
for (const [layerName, cfg] of layersToProcess as any) {
59+
const tableName = (cfg as LayerConfig).table || layerName;
60+
const table = tables.find((t) => t.name === tableName);
61+
if (!table) continue;
62+
if (!table.columns.some((c: any) => c.name.toLowerCase() === 'geometry')) continue;
63+
const layerFeatures = await fetchGeoJSONFeatures(db, table, layerName, cfg);
64+
features.push(...layerFeatures);
65+
}
66+
return features;
67+
}

src/types/spl.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
declare module 'spl.js';

0 commit comments

Comments
 (0)