Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
64 changes: 64 additions & 0 deletions src/routes/taxi/availability/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ import type { Actions, RequestEvent } from './$types';
import { fail } from '@sveltejs/kit';
import { msg } from '$lib/msg';
import { readInt } from '$lib/server/util/readForm';
import type { UnixtimeMs } from '$lib/util/UnixtimeMs';
import type { Range } from './Range';
import { split } from './Range';
import { groupBy } from '$lib/util/groupBy';
import { Interval } from '$lib/server/util/interval';

export async function load(event) {
const companyId = event.locals.session?.companyId;
Expand Down Expand Up @@ -56,9 +61,68 @@ export async function load(event) {
company.lat !== null &&
company.lng !== null;

// HEATMAP
const heatmapInfos = await db
.selectFrom('company as company1')
.innerJoin('company as company2', 'company1.zone', 'company2.zone')
.innerJoin('vehicle', 'company2.id', 'vehicle.company')
.innerJoin('availability', 'vehicle.id', 'availability.vehicle')
.where('availability.startTime', '<', toTime.getTime())
.where('availability.endTime', '>', fromTime.getTime())
.where('company1.id', '=', companyId)
.where('company2.id', '!=', companyId)
.select([
'availability.startTime',
'availability.endTime',
'availability.vehicle',
'vehicle.company'
])
.execute();

const mergedheatinfos = groupBy(
heatmapInfos,
(a) => a.vehicle,
(a) => new Interval(a.startTime, a.endTime)
);
mergedheatinfos.forEach((heatmap, vehicle) =>
mergedheatinfos.set(vehicle, Interval.merge(heatmap))
);

type heatinfo = {
cell: Range;
heat: number;
};
const heatarray: heatinfo[] = [];
const isAInsideB = (rangeA: Range, Bstart: UnixtimeMs, Bend: UnixtimeMs) => {
return (
rangeA.startTime >= Bstart &&
rangeA.startTime < Bend &&
rangeA.endTime >= Bstart &&
rangeA.endTime <= Bend
);
};
const range: Range = { startTime: fromTime.getTime(), endTime: toTime.getTime() };
const hours = split(range, 60);
let heatcount = 0;
for (const hour of hours) {
const cell = split(hour, 15);
for (const onecell of cell) {
mergedheatinfos.forEach((heatIntervals) => {
for (const interval of heatIntervals) {
if (isAInsideB(onecell, interval.startTime, interval.endTime)) {
heatcount++;
}
}
});
heatarray.push({ cell: onecell, heat: heatcount });
heatcount = 0;
}
}

return {
tours: await tours,
vehicles: await vehicles,
heatarray,
utcDate,
companyDataComplete,
companyCoordinates: companyDataComplete
Expand Down
89 changes: 72 additions & 17 deletions src/routes/taxi/availability/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,14 @@
import type { Tours } from '$lib/server/db/getTours';
import Message from '$lib/ui/Message.svelte';
import type { UnixtimeMs } from '$lib/util/UnixtimeMs';
import type { Range } from './Range';
import { split } from './Range';
import type { LngLatLike } from 'maplibre-gl';
const { data, form } = $props();
type Vehicle = NonNullable<typeof data.vehicles>[0];
type Range = {
startTime: UnixtimeMs;
endTime: UnixtimeMs;
};
// ===
// API
// ---
Expand Down Expand Up @@ -133,18 +130,6 @@
const isAvailable = (v: Vehicle, cell: Range) => v.availability.some((a) => overlaps(a, cell));
const split = (range: Range, size: number): Array<Range> => {
let cells: Array<Range> = [];
let prev = new Date(range.startTime);
let t = new Date(range.startTime);
t.setMinutes(t.getMinutes() + size);
for (; t.getTime() <= range.endTime; t.setMinutes(t.getMinutes() + size)) {
cells.push({ startTime: prev.getTime(), endTime: t.getTime() });
prev = new Date(t);
}
return cells;
};
// =========
// Selection
// ---------
Expand Down Expand Up @@ -299,6 +284,45 @@
return 'bg-yellow-100';
}
};
const heatmapColor = (cell: Range) => {
let max = 20;
for (let heat of data.heatarray) {
if (heat.cell.startTime == cell.startTime && heat.cell.endTime == cell.endTime) {
// logarithmisch für stark schwankende Werte:
//let normval = Math.floor(10 * (Math.log(heat.heat + 1)) / Math.log(max + 1));
let normval = Math.floor((heat.heat / max) * 10);
normval = heat.heat > 0 ? Math.max(1, normval) : 0;
Comment on lines +294 to +295
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nicht nachvollziehabar aus Nutzersicht.

switch (normval) {
case 0:
return;
case 1:
return 'bg-rose-100';
case 2:
return 'bg-rose-200';
case 3:
return 'bg-rose-300';
case 4:
return 'bg-rose-400';
case 5:
return 'bg-rose-500';
case 6:
return 'bg-rose-600';
case 7:
return 'bg-rose-700';
case 8:
return 'bg-rose-800';
case 9:
return 'bg-rose-900';
case 10:
return 'bg-rose-950';
default:
return;
Comment on lines +297 to +320
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unklar aus Nutzersicht was die Farbe bedeutet.

}
}
}
return;
};
</script>

<svelte:window onmouseup={() => selectionFinish()} />
Expand Down Expand Up @@ -395,6 +419,37 @@
{/each}
</tr>
{/each}
<tr>
<td
class="h-full pr-2 align-middle font-mono text-sm font-semibold leading-none tracking-tight"
>
<HoverCard.Root>
<HoverCard.Trigger>{'Auslastung'}</HoverCard.Trigger>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warum Auslastung?
Warum mit {'?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ich habe nach einem kurzen Wort gesucht, damit es gut in die Übersicht passt. Es sollte beschreiben, wie sehr der Zeitslot von den anderen Taxiunternehmern ausgelastet ist.

<HoverCard.Content>
<!-- Anpassen, wenn logarithmische Skalierung verwendet wird. -->
Eine Heatmap, die die Verfügbarkeiten der anderen Taxiunternehmer anzeigt. Farbcodierung:
linear, [1, 10]
Comment on lines +430 to +431
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unklar aus Nutzersicht

</HoverCard.Content>
</HoverCard.Root>
</td>
{#each split(range, 60) as x}
<td>
<table class="w-full">
<tbody>
<tr>
{#each split(x, 15) as cell}
<td>
<div
class={['w-8', 'h-8', 'border', 'rounded-md', heatmapColor(cell)].join(' ')}
></div>
</td>
{/each}
</tr>
</tbody>
</table>
</td>
{/each}
</tr>
</tbody>
</table>
{/snippet}
Expand Down
18 changes: 18 additions & 0 deletions src/routes/taxi/availability/Range.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { UnixtimeMs } from '$lib/util/UnixtimeMs';

export type Range = {
startTime: UnixtimeMs;
endTime: UnixtimeMs;
};

export function split(range: Range, size: number): Array<Range> {
const cells: Array<Range> = [];
let prev = new Date(range.startTime);
const t = new Date(range.startTime);
t.setMinutes(t.getMinutes() + size);
for (; t.getTime() <= range.endTime; t.setMinutes(t.getMinutes() + size)) {
cells.push({ startTime: prev.getTime(), endTime: t.getTime() });
prev = new Date(t);
}
return cells;
}