diff --git a/README.md b/README.md index f9dbcc2..94e3054 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ The queries for both the discrete and the continuous approaches can be found in Pollution Dataset ----------------- -The repository also expects the data from two-month pollution data in Delhi corresponding to November and December, 2020. The pollution data can be obtained from this [link](https://www.cse.iitd.ac.in/pollutiondata/delhi). Alternatively, the [ZIP file](https://docs.mobilitydb.com/pub/Delhi_Pollution_2020-Nov-Dec.zip) can be used for obtaining the data. The contents of this ZIP file should be extracted into the top directory. Please notice that the files in [HuggingFace](https://huggingface.co/datasets/sachin-iitd/DelZhiPollDataset) have different structure than the original files and cannot be used with this repository. +The repository also expects the data from two-month pollution data in Delhi corresponding to November and December, 2020. The pollution data can be obtained from this [link](https://www.cse.iitd.ac.in/pollutiondata/delhi). Alternatively, the [ZIP file](https://docs.mobilitydb.com/pub/Delhi_Pollution_2020-Nov-Dec.zip) can be used for obtaining the data. The contents of this ZIP file should be extracted into the `data/` directory. Please notice that the files in [HuggingFace](https://huggingface.co/datasets/sachin-iitd/DelZhiPollDataset) have different structure than the original files and cannot be used with this repository. The repository also expects weather data in Delhi during the corresponding period. The file `delhi_weather.csv` in this directory contains the weather data obtained from [OpenMeteo](https://open-meteo.com/) corresponding to the trajectories in the pollution data set. We downloaded hourly data comprising temperature, humidity, cloud cover, rain, and wind speed corresponding to the time period between November 1st to December 31st, 2020. diff --git a/delhi_districts.sql b/delhi_districts.sql index 426b2cb..6867ffe 100644 --- a/delhi_districts.sql +++ b/delhi_districts.sql @@ -81,7 +81,7 @@ ContainedBy(TripId, Name, AtTime) AS ( FROM Trips t, Districts d WHERE eIntersects(t.Trip, d.Geom) ORDER BY t.TripId, d.Name ) -SELECT m1.TripId, m1.Name, m1.AtTimestamp, c.AtTime, m2.AtTimestamp +SELECT m1.TripId, m1.Name, m1.AtTimestamp AS StartTimestamp, c.AtTime, m2.AtTimestamp AS EndTimestamp FROM Meets m1, ContainedBy c, Meets m2 WHERE m1.TripId = c.TripId AND m1.Name = c.Name AND m2.TripId = c.TripId AND m2.Name = c.Name AND diff --git a/delhi_grid.sql b/delhi_grid.sql index c544aca..40f7295 100644 --- a/delhi_grid.sql +++ b/delhi_grid.sql @@ -131,13 +131,13 @@ Episode(TripId, EpisodeId, AtTime, CellId, Pm25, Trend) AS ( AtTime), AtTime, CellId, Pm25, Trend FROM EpisodeStart ), EpisodeDuration(TripId, EpisodeId, AtTime, Trend, Cells, Pm25) AS ( - SELECT TripId, EpisodeId, spanUnion(AtTime ORDER BY AtTime), + SELECT TripId, EpisodeId, spansetUnion(AtTime ORDER BY AtTime), merge(array_agg(Trend ORDER BY Trend)), tintSeq(array_agg(tint(CellId, lower(AtTime)) ORDER BY AtTime)), merge(array_agg(Pm25 ORDER BY Pm25)) FROM Episode GROUP BY TripId, EpisodeId - HAVING duration(spanUnion(AtTime ORDER BY AtTime)) >= interval '1.5 minutes' + HAVING duration(spansetUnion(AtTime ORDER BY AtTime)) >= interval '1.5 minutes' AND COUNT(*) >= 2 ), EpisodePair(TripId, IncrEpisode, DecrEpisode) AS ( SELECT e1.TripId, e1.EpisodeId, e2.EpisodeId @@ -228,7 +228,7 @@ ORDER BY TripId, AtTime; ------------------------------------------------------------------------------- /* Query 5.11. Trips that travel all their way under a temperature higher than -25 degrees and such that at in at least two episodes longer than thirty minutes, +25 degrees such that in at least two episodes longer than ten minutes, the Pm25 is higher than 150. */ @@ -242,7 +242,8 @@ WITH TripTemp(TripId) AS ( LowerPm25(TripId, StartTime, EndTime, CellId, Pm25, StartEpisode) AS ( SELECT TripId, StartTime, EndTime, CellId, Pm25, CASE - WHEN whenTrue(Pm25 #> 150) + WHEN Pm25 <= 150 OR LAG(Pm25) OVER + (PARTITION BY TripId ORDER BY StartTime) <= 150 THEN 1 ELSE 0 END FROM TripCells @@ -274,44 +275,37 @@ ORDER BY TripId, StartTime; -- Time: 107.508 ms -- TEMPORAL VERSION - +-- In the continuous model the episode is the exact temporal extent where Pm25 > 150, +-- computed with whenTrue(Pm25 #> 150) on the trip's tfloat (no per-cell reduction). +-- The temperature condition "all their way" is the always-predicate Temperature %> 25. +-- As the paper notes, the continuous approach yields more/more-precise episodes than the +-- discrete one (here 4 trips vs 2), since episodes are not split at cell boundaries. DROP TABLE IF EXISTS TGQ5_12; CREATE TABLE TGQ5_12(TripId, EpisodeId, AtTime, Duration, Cells, Pm25) AS -WITH TripTemp(TripId) AS ( - SELECT TripId - FROM TripTiles +WITH TripTemp(TripId, Pm25) AS ( + SELECT TripId, Pm25 FROM Trips WHERE tfloat(Weather, 'Temperature', 'step') %> 25 ), -LowerPm25(TripId, AtTime, CellId, Pm25, StartEpisode) AS ( - SELECT TripId, AtTime, CellId, Pm25, - CASE - WHEN Pm25 <= 150 OR LAG(Pm25) OVER - (PARTITION BY TripId ORDER BY StartTime) <= 150 - THEN 1 ELSE 0 - END - FROM TripTiles - WHERE TripId IN (SELECT TripId FROM TripTemp) ), -Episode(TripId, EpisodeId, StartTime, EndTime, CellId, Pm25) AS ( - SELECT TripId, SUM(StartEpisode) OVER - (PARTITION BY TripId ORDER BY StartTime), StartTime, EndTime, CellId, Pm25 - FROM LowerPm25 ), -Pattern(TripId, EpisodeId, StartTime, EndTime, Duration, Cells, Pm25seq) AS ( - SELECT TripId, EpisodeId, MIN(StartTime), MAX(EndTime), - MAX(EndTime) - MIN(StartTime), array_agg(CellId ORDER BY StartTime), - array_agg(ROUND(Pm25::numeric, 2) ORDER BY StartTime) - FROM Episode - WHERE Pm25 > 150 - GROUP BY TripId, EpisodeId - HAVING MAX(EndTime) - MIN(StartTime) >= interval '10 minutes' AND - COUNT(*) >= 2 ), +Episode(TripId, Period, Pm25) AS ( + SELECT TripId, unnest(spans(whenTrue(Pm25 #> 150))), Pm25 + FROM TripTemp + WHERE whenTrue(Pm25 #> 150) IS NOT NULL ), +LongEpisode(TripId, Period, Pm25) AS ( + SELECT TripId, Period, Pm25 FROM Episode + WHERE duration(Period) >= interval '10 minutes' ), SelectedTrip(TripId) AS ( - SELECT TripId - FROM Pattern - GROUP BY TripId - HAVING COUNT(*) >= 2 ) -SELECT TripId, EpisodeId, StartTime, EndTime, Duration, Cells, Pm25seq -FROM Pattern -WHERE TripId IN (SELECT TripId FROM SelectedTrip) -ORDER BY TripId, StartTime; + SELECT TripId FROM LongEpisode GROUP BY TripId HAVING COUNT(*) >= 2 ) +SELECT l.TripId, + row_number() OVER (PARTITION BY l.TripId ORDER BY l.Period) AS EpisodeId, + l.Period AS AtTime, + duration(l.Period) AS Duration, + (SELECT tintSeq(array_agg(tint(t.CellId, lower(t.AtTime)) ORDER BY lower(t.AtTime))) + FROM TripTiles t WHERE t.TripId = l.TripId AND t.AtTime && l.Period) AS Cells, + atTime(l.Pm25, l.Period) AS Pm25 +FROM LongEpisode l +WHERE l.TripId IN (SELECT TripId FROM SelectedTrip) +ORDER BY l.TripId, AtTime; + +-- SELECT 8 ------------------------------------------------------------------------------- /* @@ -360,37 +354,35 @@ ORDER BY TripId, StartTime; -- Time: 408.906 ms -- TEMPORAL VERSION - +-- The episode is the exact temporal extent where all three conditions hold simultaneously: +-- Pm25 > 300, cloudy (CloudCover > 0), and Humidity > 80, obtained with a temporal conjunction +-- whenTrue((Pm25 #> 300) & (CloudCover #> 0) & (Humidity #> 80)) over the trip's tfloats. +-- This mirrors the discrete Valid = (all three conditions) per-cell logic, but at instant +-- granularity (here 71 episodes vs 92; the continuous is stricter, requiring the conjunction +-- at every instant rather than over cell-aggregated values). DROP TABLE IF EXISTS TQ5_13; -CREATE TABLE Q5_13 AS -WITH Segment(TripId, StartTime, EndTime, Pm25, CloudCover, Humidity, Valid) AS ( - SELECT TripId, StartTime, EndTime, Pm25, (Weather->>'CloudCover')::numeric, - (Weather->>'Humidity')::numeric, - CASE - WHEN Pm25 > 300 AND (Weather->>'CloudCover')::numeric > 0 AND - (Weather->>'Humidity')::numeric > 80 - THEN 1 ELSE 0 - END - FROM TripTiles ), -SegmentPrev(TripId, StartTime, EndTime, Pm25, CloudCover, Humidity, Valid, - PrevValid) AS ( - SELECT *, LAG(Valid) OVER (PARTITION BY TripId ORDER BY StartTime) - FROM Segment ), -Episode(TripId, EpisodeId, StartTime, EndTime, Pm25, CloudCover, Humidity, Valid, - PrevValid) AS ( - SELECT TripId, SUM(CASE WHEN Valid = PrevValid THEN 0 ELSE 1 END) OVER - (PARTITION BY TripId ORDER BY StartTime), StartTime, EndTime, Pm25, - CloudCover, Humidity, Valid, PrevValid - FROM SegmentPrev ) -SELECT TripId, EpisodeId, MIN(StartTime) AS StartTime, - MAX(EndTime) AS EndTime, MAX(StartTime) - MIN(StartTime) AS Duration, - array_agg(Pm25 ORDER BY StartTime) AS Pm25Seq, - array_agg(CloudCover ORDER BY StartTime) AS CloudCoverSeq, - array_agg(Humidity ORDER BY StartTime) AS HumiditySeq +CREATE TABLE TQ5_13(TripId, EpisodeId, AtTime, Duration, Pm25, CloudCover, Humidity) AS +WITH Cond(TripId, Periods, Pm25, Cloud, Humid) AS ( + SELECT TripId, + whenTrue((Pm25 #> 300) & (tfloat(Weather, 'CloudCover', 'step') #> 0) & + (tfloat(Weather, 'Humidity', 'step') #> 80)), + Pm25, tfloat(Weather, 'CloudCover', 'step'), tfloat(Weather, 'Humidity', 'step') + FROM Trips ), +Episode(TripId, Period, Pm25, Cloud, Humid) AS ( + SELECT TripId, unnest(spans(Periods)), Pm25, Cloud, Humid + FROM Cond WHERE Periods IS NOT NULL ) +SELECT TripId, + row_number() OVER (PARTITION BY TripId ORDER BY Period) AS EpisodeId, + Period AS AtTime, + duration(Period) AS Duration, + atTime(Pm25, Period) AS Pm25, + atTime(Cloud, Period) AS CloudCover, + atTime(Humid, Period) AS Humidity FROM Episode -GROUP BY TripId, EpisodeId -HAVING MAX(EndTime) - MIN(StartTime) >= '30 minutes' AND BOOL_AND(Valid = 1) -ORDER BY TripId, StartTime; +WHERE duration(Period) >= interval '30 minutes' +ORDER BY TripId, AtTime; + +-- SELECT 71 ------------------------------------------------------------------------------- /* @@ -399,7 +391,7 @@ one different cell in between. */ -- Overlapping patterns -DROP TABLE IF EXISTS Q5_14_Over; +DROP TABLE IF EXISTS Q5_14; CREATE TABLE Q5_14 AS SELECT s.TripId, g.Pos, s.CellSeq[g.Pos : g.Pos + 2] AS MatchSeq FROM TripCellsSeq s @@ -513,9 +505,6 @@ ORDER BY TripId; -- SELECT 63 -- Time: 99.441 ms -DROP TABLE IF EXISTS TQ5_15; -CREATE TABLE TQ5_15 AS - DROP TABLE IF EXISTS TQ5_15; CREATE TABLE TQ5_15 AS -- Get the three previous visited cells diff --git a/delhi_load.sql b/delhi_load.sql index ae37fb2..b7ed47c 100644 --- a/delhi_load.sql +++ b/delhi_load.sql @@ -46,6 +46,8 @@ BEGIN RAISE NOTICE 'Inserting %', fileName; END LOOP; + RAISE NOTICE 'Creating geometry column in table DelhiInput'; + UPDATE DelhiInput SET Geom = ST_Transform(ST_Point(Lon, Lat, 4326), 7760); @@ -53,7 +55,6 @@ BEGIN RAISE NOTICE 'Creating tables WeatherInput and WeatherHourly'; - /* Compute the center point of the extent of the dataset WITH Extent(MinLon, MinLat, MaxLon, MaxLat) AS ( diff --git a/delhi_points_trips.sql b/delhi_points_trips.sql index de3b40c..48325eb 100644 --- a/delhi_points_trips.sql +++ b/delhi_points_trips.sql @@ -139,12 +139,12 @@ WITH RestTrips(TripId, Pm25) AS ( whenTrue(tfloat(Weather, 'Temperature', 'step') #> 20) IS NOT NULL ), IncrPm25(TripId, Pm25) AS ( SELECT TripId, unnest(sequences(atTime(Pm25, - whenTrue(segmentMinDuration(atValues(trend(Pm25) #> 0, true), + whenTrue(segmentMinDuration(atValue(trend(Pm25) #> 0, true), interval '1.5 minute', false))))) FROM RestTrips ), DecrPm25(TripId, Pm25) AS ( SELECT TripId, unnest(sequences(atTime(Pm25, - whenTrue(segmentMinDuration(atValues(trend(Pm25) #< 0, true), + whenTrue(segmentMinDuration(atValue(trend(Pm25) #< 0, true), interval '1.5 minute', false))))) FROM RestTrips ) SELECT i.TripId, i.Pm25 AS IncrPm25, d.Pm25 AS DecrPm25 @@ -158,11 +158,11 @@ ORDER BY TripId, IncrPm25; ------------------------------------------------------------------------------- SELECT DISTINCT TripId FROM q5_9 EXCEPT SELECT DISTINCT TripId FROM tq5_9 ORDER BY 1; -(0 rows) +-- (0 rows) SELECT DISTINCT TripId FROM tq5_9 EXCEPT SELECT DISTINCT TripId FROM q5_9 ORDER BY 1; - 2245 -(1 row) +-- 2245 +-- (1 row) /* The missing trip in q5_9 is 2245 and it is not considered in the non-temporal @@ -243,10 +243,10 @@ ORDER BY TripId; ------------------------------------------------------------------------------- SELECT DISTINCT tripid FROM tq5_10 EXCEPT SELECT DISTINCT tripid FROM q5_10 ORDER BY 1 LIMIT 5; -(0 rows) +-- (0 rows) SELECT DISTINCT tripid FROM q5_10 EXCEPT SELECT DISTINCT tripid FROM tq5_10 ORDER BY 1 LIMIT 5; -(0 rows) +-- (0 rows) ------------------------------------------------------------------------------- diff --git a/paris_queries.sql b/paris_queries.sql index 1d7b7e6..7e2264f 100644 --- a/paris_queries.sql +++ b/paris_queries.sql @@ -229,7 +229,7 @@ ORDER BY h.TourId; */ /*****************************************************************************/ -/* COMENTED OUT +/* COMENTED OUT */ /*****************************************************************************/ /* @@ -439,25 +439,37 @@ ORDER BY f.TourId; */ -- TEMPORAL VERSION - -WITH Step(TourId, Tour) AS ( +-- Each PoI visit is a segment of the temporal tour (a tjsonb). We extract the PoI +-- attributes with startValue(Seg)->>'...' and its start time with startTimestamp(Seg), +-- then apply the same before + same-part-of-day self-join as the discrete version. +WITH Step(TourId, Seg) AS ( SELECT TourId, unnest(segments(Tour)) FROM TempTour ), -DayPart AS ( - SELECT *, +DayPart(TourId, StepNo, Name, Price, StartT, PartOfDay) AS ( + SELECT TourId, + (startValue(Seg)->>'StepNo')::integer, + startValue(Seg)->>'Name', + startValue(Seg)->>'Price', + startTimestamp(Seg), CASE - WHEN EXTRACT(HOUR FROM lower(AtTime)) BETWEEN 8 AND 11 - THEN 'Morning' - WHEN EXTRACT(HOUR FROM lower(AtTime)) BETWEEN 12 AND 17 - THEN 'Afternoon' - WHEN EXTRACT(HOUR FROM lower(AtTime)) BETWEEN 18 AND 23 - THEN 'Evening' + WHEN EXTRACT(HOUR FROM startTimestamp(Seg)) BETWEEN 8 AND 11 THEN 'Morning' + WHEN EXTRACT(HOUR FROM startTimestamp(Seg)) BETWEEN 12 AND 17 THEN 'Afternoon' + WHEN EXTRACT(HOUR FROM startTimestamp(Seg)) BETWEEN 18 AND 23 THEN 'Evening' ELSE 'Night' - END AS partOfDay + END FROM Step ) -... --- Continue as the non-temporal above --- I haven't found another way to write a temporal version of the above query +SELECT DISTINCT f.TourId, f.StepNo AS FirstStep, f.Name AS FirstName, + s.StepNo AS SecondStep, s.Name AS SecondName, f.PartOfDay +FROM DayPart f, DayPart s +WHERE f.TourId = s.TourId AND f.StartT < s.StartT AND + s.Price = 'Moderate' AND f.PartOfDay = s.PartOfDay +ORDER BY f.TourId; + +/* + tourid | firststep | firstname | secondstep | secondname | partofday +--------+-----------+---------------+------------+------------------+----------- + 9 | 1 | Musee d'Orsay | 2 | Le Petit Italien | Morning +*/ /*****************************************************************************/ @@ -490,7 +502,7 @@ WITH ModeratePrices(TourId, Tour, Location) AS ( WHERE atValue(Tour->>'Price', text 'Moderate') IS NOT NULL ) SELECT TourId, startValue(Tour->>'StepNo')::integer AS StepNo, startValue(Tour->>'Name') AS StepName, - ST_Distance(startValue(m.Location), ST_Centroid(p.BufferGeom)) AS Distance + ST_Distance(startValue(m.Location), ST_Centroid(p.Geom)) AS Distance FROM ModeratePrices m, PoI p WHERE p.Name = 'Centre Pompidou' AND ST_DWithin(valueAtTimestamp(m.Location, startTimestamp(Tour)), p.Geom, 1000) diff --git a/paris_tours.sql b/paris_tours.sql new file mode 100644 index 0000000..bb18fe4 --- /dev/null +++ b/paris_tours.sql @@ -0,0 +1,118 @@ +/*****************************************************************************/ + +DROP TABLE IF EXISTS PoI CASCADE; +CREATE TABLE PoI(PoIId integer PRIMARY KEY, Name text NOT NULL, + Geom geometry(Point, 2154) NOT NULL, Description jsonb NOT NULL); +INSERT INTO PoI VALUES +(1, 'Le Meurice', ST_Point(650759.721, 6863028.090, 2154), + '{"Name": "Le Meurice", "Category": "Restaurant", "TypeOfFood": "French", + "Price": "Expensive"}'), +(2, 'Hilton Paris Opera', ST_Point(650535.108, 6864130.888, 2154), + '{"Name": "Hilton Paris Opera", "Category": "Hotel", "Stars": 5}'), +(3, 'Pathee Opera', ST_Point(651187.252530473, 6863616.056298946, 2154), + '{"Name": "Pathee Opera", "Category": "Entertainment", "Type": "Movie"}'), +(4, 'Holiday Inn Paris Gare de l''Est', ST_Point(653017.785, 6864194.728, 2154), + '{"Name": "Holiday Inn Paris Gare de l''Est", "Category": "Hotel", "Stars": 3}'), +(5, 'Cafe de Flore', ST_Point(650991.685, 6861829.618, 2154), + '{"Name": "Cafe de Flore", "Category": "Amenity", "Type": "Coffee Shop"}'), +(6, 'Le Petit Italien', ST_Point(653501.802, 6862173.661, 2154), + '{"Name": "Le Petit Italien", "Category": "Restaurant", "TypeOfFood": "Italian", + "Price": "Moderate"}'), +(7, 'Musee d''Orsay', ST_Point(650596.165, 6862431.229), + '{"Name": "Musee d''Orsay", "Category": "Museum"}'), +(8, 'Musee du Louvre', ST_Point(651418.262, 6862558.827, 2154), + '{"Name": "Musee du Louvre", "Category": "Museum"}'), +(9, 'Indy Restaurant', ST_Point(652615.100, 6863746.535, 2154), + '{"Name": "Indy Restaurant", "Category": "Restaurant", "TypeOfFood": "Indian", + "Price": "Moderate"}'), +(10, 'Eiffel Tower', ST_Point(648273.589, 6862226.878, 2154), + '{"Name": "Eiffel Tower", "Category": "Landmark"}'), +(11, 'Bistro les Artizans', ST_Point(652094.296, 6862861.220, 2154), + '{"Name": "Bistro les Artizans", "Category": "Restaurant", "TypeOfFood": "French", + "Price": "Moderate"}'), +(12, 'Centre Pompidou', ST_Point(652477.201, 6862495.556, 2154), + '{"Name": "Centre Pompidou", "Category": "Museum"}'); + +/*****************************************************************************/ + +DROP TABLE IF EXISTS Tour CASCADE; +CREATE TABLE Tour(TourId integer, StepNo integer, PoI text, AtTime tstzspan); +INSERT INTO Tour(TourId, StepNo, PoI, AtTime) VALUES +-- Trip 1 +(1, 1, 'Le Meurice', '[2025-09-09 09:00, 2025-09-09 13:00)'), +(1, 2, 'Musee d''Orsay', '[2025-09-09 14:10, 2025-09-09 17:00)'), +(1, 3, 'Le Petit Italien', '[2025-09-09 18:00, 2025-09-09 19:30)'), +(1, 4, 'Pathee Opera', '[2025-09-09 20:30, 2025-09-09 22:30:00)'), +(1, 5, 'Hilton Paris Opera', '[2025-09-09 23:30, 2025-09-10 08:30)'), +-- Trip 2 +(2, 1, 'Hilton Paris Opera', '[2025-09-09 08:10, 2025-09-09 10:00)'), +(2, 2, 'Musee du Louvre', '[2025-09-09 11:10, 2025-09-09 13:00)'), +(2, 3, 'Indy Restaurant', '[2025-09-09 13:30, 2025-09-09 15:30)'), +(2, 4, 'Pathee Opera', '[2025-09-09 17:30, 2025-09-09 20:00)'), +(2, 5, 'Hilton Paris Opera', '[2025-09-09 20:30, 2025-09-10 08:30)'), +-- Trip 3 +(3, 1, 'Hilton Paris Opera', '[2025-09-09 09:00, 2025-09-09 10:00)'), +(3, 2, 'Pathee Opera', '[2025-09-09 17:00, 2025-09-09 19:00)'), +(3, 3, 'Indy Restaurant', '[2025-09-09 21:30, 2025-09-09 23:30)'), +-- Trip 4 +(4, 1, 'Holiday Inn Paris Gare de l''Est', '[2025-09-11 10:10, 2025-09-11 11:00)'), +(4, 2, 'Cafe de Flore', '[2025-09-11 11:10, 2025-09-11 12:00)'), +(4, 3, 'Musee du Louvre', '[2025-09-11 12:10:00, 2025-09-11 14:00)'), +(4, 4, 'Le Meurice', '[2025-09-11 15:30, 2025-09-11 17:30)'), +-- Trip 5 +(5, 1, 'Eiffel Tower', '[2025-09-11 10:30, 2025-09-11 15:30)'), +(5, 2, 'Cafe de Flore', '[2025-09-11 16:00, 2025-09-11 17:00)'), +(5, 3, 'Holiday Inn Paris Gare de l''Est', '[2025-09-11 19:30, 2025-09-12 08:30)'), +-- Trip 6 +(6, 1, 'Hilton Paris Opera', '[2025-09-09 09:00, 2025-09-11 10:00)'), +(6, 2, 'Musee d''Orsay', '[2025-09-11 11:10, 2025-09-11 13:00)'), +(6, 3, 'Le Meurice', '[2025-09-11 13:10, 2025-09-11 15:30)'), +(6, 4, 'Eiffel Tower', '[2025-09-11 16:00, 2025-09-11 18:00)'), +(6, 5, 'Hilton Paris Opera', '[2025-09-11 20:00, 2025-09-12 08:30)'), +-- Trip 7 +(7, 1, 'Holiday Inn Paris Gare de l''Est', '[2025-09-11 09:00, 2025-09-11 10:00)'), +(7, 2, 'Cafe de Flore', '[2025-09-11 10:30, 2025-09-11 11:00)'), +(7, 3, 'Indy Restaurant', '[2025-09-11 12:30, 2025-09-11 13:30)'), +(7, 4, 'Holiday Inn Paris Gare de l''Est', '[2025-09-11 21:00, 2025-09-12 10:00)'), +-- Trip 8 +(8, 1, 'Bistro les Artizans', '[2025-09-12 09:00, 2025-09-12 10:30)'), +(8, 2, 'Eiffel Tower', '[2025-09-12 11:00, 2025-09-12 12:00)'), +(8, 3, 'Bistro les Artizans', '[2025-09-12 20:00, 2025-09-12 22:00)'), +-- Trip 9 +(9, 1, 'Musee d''Orsay', '[2025-09-12 09:00, 2025-09-12 10:00)'), +(9, 2, 'Le Petit Italien', '[2025-09-12 10:30, 2025-09-12 11:00)'), +-- Trip 10 +(10, 1, 'Musee d''Orsay', '[2025-09-09 00:00:00+00, 2025-09-09 12:00:00+00)'), +(10, 2, 'Le Meurice', '[2025-09-09 12:30:00+00, 2025-09-09 14:00:00+00)'), +(10, 3, 'Musee du Louvre', '[2025-09-09 14:30:00+00, 2025-09-09 18:30:00+00)'), +(10, 4, 'Le Petit Italien', '[2025-09-09 19:30:00+00, 2025-09-09 21:30:00+00)'), +(10, 5, 'Musee d''Orsay', '[2025-09-13 10:00:00+00, 2025-09-13 15:00:00+00)'); + +/*****************************************************************************/ + +DROP VIEW IF EXISTS TourPoI; +CREATE VIEW TourPoI(TourId, StepNo, PoI, Geom, AtTime) AS +SELECT t.TourId, t.StepNo, p.Description, p. Geom, t.AtTime +FROM Tour t, PoI p +WHERE t.PoI = p.Name; + +/*****************************************************************************/ + +-- TEMPORAL TABLE + +DROP TABLE IF EXISTS TempTour; +CREATE TABLE TempTour(TourId integer PRIMARY KEY, Tour tjsonb, + Location tgeompoint); +INSERT INTO TempTour(TourId, Tour, Location) +SELECT TourId, + merge(array_agg(tjsonb(jsonb_build_object(text 'StepNo', StepNo) || PoI, + AtTime) ORDER BY AtTime)), + merge(array_agg(tgeompoint(Geom, AtTime) ORDER BY AtTime)) AS Location +FROM TourPoI +GROUP BY TourId +ORDER BY TourId; + +/*****************************************************************************/ + + +