using DuckDB.NET.Data; namespace YongeStreet.Services; public record Road(int RelationId, List> Points); public record CityOverview(string Name, double TotalStreetSize, IList AvailableDates); public record CityRoadLengthStats(string Name, double MunicipalRoadLength, DateOnly Date); public class DataService { private static readonly List CITY_STREET_HIGHWAY = ["primary", "primary_link", "secondary", "secondary_link", "tertiary", "tertiary_link", "residential", "unclassified"]; private readonly string path = System.Environment.GetEnvironmentVariable("PREPROCESSED_DUCKDB_FILE") ?? "/home/didier/dev/YongeStreet/real.duckdb"; public IList GetRoads(string city, DateOnly extractDate) { using var duckDBConnection = new DuckDBConnection($"Data Source={path};ACCESS_MODE=READ_ONLY"); duckDBConnection.Open(); using var command = duckDBConnection.CreateCommand(); command.CommandText = """ SELECT relation_id, points FROM city_road WHERE city_name = $city_name AND extract_date = $extract_date AND list_contains($allowed_highway, highway) """; command.Parameters.Add(new DuckDBParameter("city_name", city)); command.Parameters.Add(new DuckDBParameter("extract_date", extractDate)); command.Parameters.Add(new DuckDBParameter("allowed_highway", CITY_STREET_HIGHWAY)); using var reader = command.ExecuteReader(); var ret = new List(); while (reader.Read()) { var points = reader.GetFieldValue>>(1); ret.Add(new Road(reader.GetInt32(0), points)); } return ret; } public IList GetCityOverview() { using var duckDBConnection = new DuckDBConnection($"Data Source={path};ACCESS_MODE=READ_ONLY"); duckDBConnection.Open(); using var command = duckDBConnection.CreateCommand(); command.CommandText = """ SELECT city_name, SUM(length) FILTER (list_contains($allowed_highway, highway) AND extract_date = (Date '2025-01-01')) AS length, LIST(DISTINCT extract_date) AS available_years FROM city_road GROUP BY city_name ORDER BY length desc LIMIT 100 """; command.Parameters.Add(new DuckDBParameter("allowed_highway", CITY_STREET_HIGHWAY)); using var reader = command.ExecuteReader(); var ret = new List(); while (reader.Read()) ret.Add(new CityOverview(reader.GetString(0), reader.GetDouble(1), reader.GetFieldValue>(2))); return ret; } public IList GetCityRoadLengthStats(string city) { using var duckDBConnection = new DuckDBConnection($"Data Source={path};ACCESS_MODE=READ_ONLY"); duckDBConnection.Open(); using var command = duckDBConnection.CreateCommand(); command.CommandText = """ SELECT SUM(length) FILTER (list_contains($allowed_highway, highway)) AS length, extract_date AS date FROM city_road WHERE city_name = $city_name GROUP BY extract_date ORDER BY date DESC """; command.Parameters.Add(new DuckDBParameter("city_name", city)); command.Parameters.Add(new DuckDBParameter("allowed_highway", CITY_STREET_HIGHWAY)); using var reader = command.ExecuteReader(); var ret = new List(); while (reader.Read()) { ret.Add(new CityRoadLengthStats(city, reader.GetInt32(0), reader.GetFieldValue(1))); } return ret; } }