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
38 changes: 15 additions & 23 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,40 +44,32 @@ jobs:
cp -r images docbook.css html/
cp docbook.css html/

# store the documentation files
# store the documentation files (downloadable by pull-request reviewers)
- name: Upload output directory
uses: actions/upload-artifact@v4
with:
name: doc-files
path: docs
retention-days: 1


copy:
name: Deploy documentation
runs-on: ubuntu-latest
needs: build
# The gh-pages branch exists only on the canonical repository, and the
# published docs track master; deploy only from master there.
if: github.repository == 'MobilityDB/MobilityDB-BerlinMOD' && github.ref == 'refs/heads/master'

steps:
# checkout the gh-pages branch
- uses: actions/checkout@v4
# Publish to gh-pages only from master on the canonical repository. This
# is a conditional STEP, not a separate job, so pull requests show a single
# always-green "Generate documentation" check with no skipped deploy job.
- name: Check out gh-pages
if: github.event_name == 'push' && github.ref == 'refs/heads/master' && github.repository == 'MobilityDB/MobilityDB-BerlinMOD'
uses: actions/checkout@v4
with:
ref: gh-pages
path: gh-pages

# download the doc files generated above, overwriting the tracked outputs
- name: Download output directory
uses: actions/download-artifact@v4
with:
name: doc-files
path: docs

# commit and push to gh-pages with plain git (no pull, so the overwritten
# binary outputs do not abort a merge)
- name: Commit changes
# Overwrite the tracked outputs with the freshly built ones and push with
# plain git (no pull, so the replaced binary outputs do not abort a merge).
- name: Deploy documentation
if: github.event_name == 'push' && github.ref == 'refs/heads/master' && github.repository == 'MobilityDB/MobilityDB-BerlinMOD'
run: |
rm -rf gh-pages/docs
cp -r docs gh-pages/docs
cd gh-pages
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add docs
Expand Down
37 changes: 31 additions & 6 deletions BerlinMOD/benchmarks/batch/bench/bench_mbdb.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
BERLINMOD_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
QUERIES_SQL="${SCRIPT_DIR}/queries.sql" # shared single-source query set

# ── defaults ──────────────────────────────────────────────────────────────────
DBNAME="berlinmod_bench"
Expand Down Expand Up @@ -101,9 +102,9 @@ if $LOAD; then
createdb "$DBNAME" 2>/dev/null || true
LOADER=$(mktemp --suffix=.sql)
trap 'rm -f "$LOADER"' EXIT
sed "s|DATADIR|${DATADIR}|g" "${BERLINMOD_DIR}/load_mbdb.sql" > "$LOADER"
sed "s|DATADIR|${DATADIR}|g" "${SCRIPT_DIR}/load_mbdb.sql" > "$LOADER"
echo "=== Loading data ==="
_psql -f "$LOADER"
_psql -v ON_ERROR_STOP=1 -f "$LOADER"
echo " done."
fi

Expand Down Expand Up @@ -152,15 +153,39 @@ echo "=== Runs : ${RUNS} per query (queries: ${QUERIES_MSG}) ==="
echo ""

TIMEFILE=$(mktemp)
trap 'rm -f "$TIMEFILE"' EXIT
QDIR=$(mktemp -d)
trap 'rm -f "$TIMEFILE" "${LOADER:-}"; rm -rf "$QDIR"' EXIT

# ── extract each selected query from the shared queries.sql ──────────────────
# queries.sql is the single canonical source; every query is delimited by a
# `-- BerlinMOD Q<n>:` (or `-- BerlinMOD QRT:`) marker and runs to the next one.
marker_for() { # q07 -> Q7 ; q13 -> Q13 ; qrt -> QRT
local q="$1"
if [[ "$q" == "qrt" ]]; then echo "QRT"; else echo "Q$((10#${q#q}))"; fi
}
for Q in "${QUERIES[@]}"; do
M=$(marker_for "$Q")
awk -v start="-- BerlinMOD ${M}:" '
index($0, start)==1 { grab=1; print; next }
grab && /^-- BerlinMOD Q/ { exit }
grab { print }
' "$QUERIES_SQL" > "${QDIR}/${Q}.sql"
[[ -s "${QDIR}/${Q}.sql" ]] || echo " [warn] ${Q} (${M}) not found in ${QUERIES_SQL}"
done

for Q in "${QUERIES[@]}"; do
QFILE="${BERLINMOD_DIR}/${Q}.sql"
[[ -f "$QFILE" ]] || { echo " [skip] ${Q} — SQL file not found"; continue; }
QFILE="${QDIR}/${Q}.sql"
[[ -s "$QFILE" ]] || { echo " [skip] ${Q} — not found in queries.sql"; continue; }
# Validate once with ON_ERROR_STOP: a broken query is reported and EXCLUDED,
# never recorded as a phantom sub-second timing (the earlier harness bug).
if ! ERR=$(_psql -v ON_ERROR_STOP=1 -o /dev/null -f "$QFILE" 2>&1); then
echo " [FAIL] ${Q} — ${ERR##*ERROR: }"
continue
fi
printf " timing %-6s: " "$Q"
for RUN in $(seq 1 "$RUNS"); do
T0=$(date +%s%3N)
_psql -o /dev/null -f "$QFILE" 2>/dev/null || true
_psql -o /dev/null -f "$QFILE" >/dev/null 2>&1
T1=$(date +%s%3N)
ELAPSED=$((T1 - T0))
printf "%d " "$ELAPSED"
Expand Down
112 changes: 112 additions & 0 deletions BerlinMOD/benchmarks/batch/bench/load_mbdb.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*-----------------------------------------------------------------------------
-- BerlinMOD Batch Load — MobilityDB / PostgreSQL
-------------------------------------------------------------------------------

This file is part of MobilityDB.
Copyright(c) 2020-2026, Université libre de Bruxelles and MobilityDB
contributors

-------------------------------------------------------------------------------

Loads the cross-platform BerlinMOD portability export (produced by
berlinmod_portability_export() in berlinmod_export.sql) into the schema the
shared batch queries (bench/queries.sql) expect. The same CSV set feeds the
MobilityDuck and MobilitySpark runners, so all three platforms benchmark the
identical corpus with no per-tool reprojection or post-processing.

Input CSVs (DATADIR is substituted by bench_mbdb.sh):
vehicles.csv : vehId, licence, type, model
trips.csv : tripId, vehId, trip -- tgeompoint as hex-EWKB
query_licences.csv : licenceId, licence
query_instants.csv : instantId, instant
query_points.csv : pointId, geom -- geometry as EWKT
query_periods.csv : periodId, period -- tstzspan as text
query_regions.csv : regionId, geom -- geometry as EWKT

The export SRID-tags every geometry (default 4326), so the H3 prefilter
th3index(trip, 7) / geoToH3IndexSet(geom, 7) reads lat/lon directly, exactly as
a real ingest of raw GPS/AIS would build its cell index at load time.
-----------------------------------------------------------------------------*/

\set ON_ERROR_STOP on
\timing off

CREATE EXTENSION IF NOT EXISTS MobilityDB CASCADE;

-- ── Vehicles ─────────────────────────────────────────────────────────────────
DROP TABLE IF EXISTS Vehicles CASCADE;
CREATE TABLE Vehicles (
vehId integer PRIMARY KEY,
licence text,
type text,
model text
);
COPY Vehicles(vehId, licence, type, model)
FROM 'DATADIR/vehicles.csv' DELIMITER ',' CSV HEADER;

-- ── Trips ────────────────────────────────────────────────────────────────────
-- trip arrives as hex-EWKB (SRID embedded); the th3index prefilter column is
-- built here from the lat/lon trajectory, not shipped in the CSV.
DROP TABLE IF EXISTS Trips CASCADE;
DROP TABLE IF EXISTS TripsInput CASCADE;
CREATE TABLE TripsInput (tripId integer, vehId integer, trip text);
COPY TripsInput(tripId, vehId, trip)
FROM 'DATADIR/trips.csv' DELIMITER ',' CSV HEADER;

CREATE TABLE Trips AS
SELECT tripId, vehId, tgeompointFromHexEWKB(trip) AS trip
FROM TripsInput;
DROP TABLE TripsInput;

ALTER TABLE Trips ADD PRIMARY KEY (tripId);
ALTER TABLE Trips ADD COLUMN trip_h3 th3index;
UPDATE Trips SET trip_h3 = th3index(transform(trip, 4326), 7);

-- ── Query parameter tables (100-row selectors used by queries.sql) ───────────
DROP TABLE IF EXISTS QueryLicences CASCADE;
CREATE TABLE QueryLicences (licenceId integer PRIMARY KEY, licence text);
COPY QueryLicences(licenceId, licence)
FROM 'DATADIR/query_licences.csv' DELIMITER ',' CSV HEADER;

DROP TABLE IF EXISTS QueryInstants CASCADE;
CREATE TABLE QueryInstants (instantId integer PRIMARY KEY, instant timestamptz);
COPY QueryInstants(instantId, instant)
FROM 'DATADIR/query_instants.csv' DELIMITER ',' CSV HEADER;

DROP TABLE IF EXISTS QueryPeriods CASCADE;
CREATE TABLE QueryPeriods (periodId integer PRIMARY KEY, period tstzspan);
COPY QueryPeriods(periodId, period)
FROM 'DATADIR/query_periods.csv' DELIMITER ',' CSV HEADER;

-- Points carry both the parsed geometry (for the spatial predicate) and the
-- SRID-tagged EWKT text (queries.sql surfaces p.geomWKT in the result
-- projection). The exported geom column is already asEWKT output, so geomWKT
-- is that text verbatim — no ST_AsText round-trip.
DROP TABLE IF EXISTS QueryPoints CASCADE;
DROP TABLE IF EXISTS QueryPointsInput CASCADE;
CREATE TABLE QueryPointsInput (pointId integer, geom text);
COPY QueryPointsInput(pointId, geom)
FROM 'DATADIR/query_points.csv' DELIMITER ',' CSV HEADER;
CREATE TABLE QueryPoints AS
SELECT pointId,
ST_GeomFromEWKT(geom) AS geom,
geom AS geomWKT
FROM QueryPointsInput;
DROP TABLE QueryPointsInput;

DROP TABLE IF EXISTS QueryRegions CASCADE;
DROP TABLE IF EXISTS QueryRegionsInput CASCADE;
CREATE TABLE QueryRegionsInput (regionId integer, geom text);
COPY QueryRegionsInput(regionId, geom)
FROM 'DATADIR/query_regions.csv' DELIMITER ',' CSV HEADER;
CREATE TABLE QueryRegions AS
SELECT regionId, ST_GeomFromEWKT(geom) AS geom
FROM QueryRegionsInput;
DROP TABLE QueryRegionsInput;

-- ── Indexes (names match the tier-drop logic in bench_mbdb.sh) ───────────────
CREATE INDEX Trips_Trip_gist_idx ON Trips USING GIST(trip);
CREATE INDEX Trips_Trip_spgist_idx ON Trips USING SPGIST(trip);
CREATE INDEX Trips_trip_h3_gist_idx ON Trips USING GIST(trip_h3);

ANALYZE;
76 changes: 76 additions & 0 deletions BerlinMOD/benchmarks/batch/setup/generate_data.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/usr/bin/env bash
# BerlinMOD cross-platform data generator
#
# Produces the shared CSV corpus that every platform runner (bench_mbdb.sh,
# bench_mduck.sh, bench_mspark.sh) loads, so all three benchmark the identical
# data. It orchestrates the canonical MobilityDB scripts — nothing bespoke:
#
# 1. berlinmod_datagenerator.sql : SELECT berlinmod_generate(scaleFactor := S)
# generates the raw BerlinMOD trips/vehicles over the Brussels road network.
# 2. berlinmod_export.sql : SELECT berlinmod_portability_export(DIR, SRID)
# writes the cross-platform CSVs (geometries reprojected + SRID-tagged):
# vehicles.csv trips.csv query_licences.csv query_instants.csv
# query_points.csv query_periods.csv query_regions.csv
#
# PREREQUISITE (one-time, environment-specific): the generator needs the Brussels
# road-network graph (RoadSegments + Nodes) already loaded in the database,
# typically built with osm2pgrouting from an OSM extract — see the header of
# berlinmod_datagenerator.sql. Pass --no-generate to skip step 1 and export from
# a database that already holds a generated BerlinMOD instance.
#
# Usage:
# setup/generate_data.sh [options]
#
# Options:
# --scalefactor S BerlinMOD scale factor (default: 0.005 = 1620 trips)
# --output DIR Directory for the CSV corpus (default: <batch>/data)
# --dbname NAME Generation/source database (default: berlinmod_gen)
# --srid N Output SRID for geometries (default: 4326, WGS84)
# --no-generate Skip generation; export from an existing populated database

set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
BATCH_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
BERLINMOD_DIR="$(cd "${BATCH_DIR}/../.." && pwd)" # repo BerlinMOD/ (canonical scripts)

SCALEFACTOR=0.005
OUTPUT="${BATCH_DIR}/data"
DBNAME="berlinmod_gen"
SRID=4326
GENERATE=true

while [[ $# -gt 0 ]]; do
case "$1" in
--scalefactor) SCALEFACTOR="$2"; shift 2 ;;
--output) OUTPUT="$2"; shift 2 ;;
--dbname) DBNAME="$2"; shift 2 ;;
--srid) SRID="$2"; shift 2 ;;
--no-generate) GENERATE=false; shift ;;
*) echo "Unknown option: $1"; exit 1 ;;
esac
done

command -v psql >/dev/null 2>&1 || { echo "psql not found on PATH"; exit 1; }
mkdir -p "$OUTPUT"
# COPY … TO writes as the server user; give it a trailing slash and a writable dir.
OUTPUT="$(cd "$OUTPUT" && pwd)/"

_psql() { psql -d "$DBNAME" -v ON_ERROR_STOP=1 -q "$@"; }

if $GENERATE; then
echo "=== Generating BerlinMOD (scale factor ${SCALEFACTOR}) in ${DBNAME} ==="
createdb "$DBNAME" 2>/dev/null || true
_psql -c "CREATE EXTENSION IF NOT EXISTS MobilityDB CASCADE;"
_psql -f "${BERLINMOD_DIR}/berlinmod_datagenerator.sql"
_psql -c "SELECT berlinmod_generate(scaleFactor := ${SCALEFACTOR});"
fi

echo "=== Exporting cross-platform CSVs to ${OUTPUT} (SRID ${SRID}) ==="
_psql -f "${BERLINMOD_DIR}/berlinmod_export.sql"
_psql -c "SELECT berlinmod_portability_export('${OUTPUT}', ${SRID});"

echo "=== Done. Corpus:"
ls -1 "${OUTPUT}"*.csv 2>/dev/null | sed 's|^| |'
echo ""
echo "Point a runner at it, e.g.:"
echo " bench/bench_mbdb.sh --data ${OUTPUT%/} --runs 1"
Loading