added search

This commit is contained in:
didier committed 2025-01-11 21:26:33 -05:00
1 parent 0e51144845
commit 91788dd54b
11 files changed
+169 -73

No files matched your search

+9 -1
View File
@@ -7,7 +7,9 @@ namespace YongeStreet.Controllers;
public record CityModel(string City, IList<CityRoadLengthStats> CityRoadLengthStats, DateOnly Date);
public record IndexModel(IList<CityOverview> Cities);
public record IndexModel(List<CityOverview> Cities);
public record TmpModel(IList<string> Cities);
public class HomeController : Controller
{
@@ -33,6 +35,12 @@ public class HomeController : Controller
return View(new IndexModel(cities));
}
public IActionResult CityList_Partial(string? search)
{
var cities = _dataService.GetCityOverview(search??"");
return View(new IndexModel(cities));
}
public IActionResult Privacy()
{
return View();
+53 -16
View File
@@ -1,4 +1,5 @@
using DuckDB.NET.Data;
using FuzzierSharp;
namespace YongeStreet.Services;
@@ -10,15 +11,27 @@ public record CityRoadLengthStats(string Name, double MunicipalRoadLength, DateO
public class DataService
{
private static readonly List<string> CITY_STREET_HIGHWAY = ["primary",
private static readonly List<string> CITY_STREET_HIGHWAY =
[
"primary",
"primary_link",
"secondary",
"secondary",
"secondary_link",
"tertiary",
"tertiary_link",
"residential",
"unclassified"];
private readonly string path = System.Environment.GetEnvironmentVariable("PREPROCESSED_DUCKDB_FILE") ?? "/home/didier/all-dressed-programming/YongeStreet/real.duckdb";
"unclassified"
];
private readonly ILogger<DataService> _logger;
private readonly string path = Environment.GetEnvironmentVariable("PREPROCESSED_DUCKDB_FILE") ??
"/home/didier/all-dressed-programming/YongeStreet/real.duckdb";
public DataService(ILogger<DataService> logger)
{
_logger = logger;
}
public IList<Road> GetRoads(string city, DateOnly extractDate)
{
@@ -48,20 +61,20 @@ public class DataService
return ret;
}
public IList<CityOverview> GetCityOverview()
public List<CityOverview> 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,
WITH raw AS (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
ORDER BY length desc)
SELECT * FROM raw WHERE length > 1000
""";
command.Parameters.Add(new DuckDBParameter("allowed_highway", CITY_STREET_HIGHWAY));
using var reader = command.ExecuteReader();
@@ -74,7 +87,36 @@ public class DataService
return ret;
}
public List<string> GetAllCityNames()
{
using var duckDBConnection = new DuckDBConnection($"Data Source={path};ACCESS_MODE=READ_ONLY");
duckDBConnection.Open();
using var command = duckDBConnection.CreateCommand();
command.CommandText = "SELECT DISTINCT city_name FROM city_road";
using var reader = command.ExecuteReader();
var ret = new List<string>();
while (reader.Read()) ret.Add(reader.GetString(0));
return ret;
}
public List<CityOverview> GetCityOverview(string search)
{
_logger.LogWarning("Seeking " + search);
if (search == "") return GetCityOverview().Take(20).ToList();
var cityNames = GetAllCityNames();
var foundCityNames = Process.ExtractSorted(search, cityNames);
return (
from cityName in foundCityNames
join city in GetCityOverview() on cityName.Value equals city.Name
orderby cityName.Score descending
select city
).Take(20).ToList();
}
public IList<CityRoadLengthStats> GetCityRoadLengthStats(string city)
{
using var duckDBConnection = new DuckDBConnection($"Data Source={path};ACCESS_MODE=READ_ONLY");
@@ -95,15 +137,10 @@ public class DataService
var ret = new List<CityRoadLengthStats>();
while (reader.Read())
{
ret.Add(new CityRoadLengthStats(city,
reader.GetInt32(0),
ret.Add(new CityRoadLengthStats(city,
reader.GetInt32(0),
reader.GetFieldValue<DateOnly>(1)));
}
return ret;
}
}
+5 -2
View File
@@ -12,7 +12,8 @@
<div class="uk-height-large uk-flex uk-flex-center uk-flex-middle uk-background-cover uk-light"
style=""
id="leaflet-map"
data-city="@Model.City">
data-city="@Model.City"
data-date="@Model.Date.ToString("yyyy-MM-dd")">
</div>
<table class="uk-table uk-table-striped">
@@ -50,4 +51,6 @@
</tbody>
</table>
<script src="~/js/quebec-province-map.js" type="module"></script>
<script src="~/js/quebec-province-map.js" type="module"></script>
@@ -0,0 +1,16 @@
@model YongeStreet.Controllers.IndexModel
@{
Layout = "";
}
@foreach(var city in Model.Cities)
{
<tr>
<td>@Html.ActionLink(city.Name, "City", "Home", new {City=city.Name})</td>
<td>@((city.TotalStreetSize / 1000 ).ToString("N"))</td>
<td>@string.Join(", ", city.AvailableDates)</td>
</tr>
}
+21 -11
View File
@@ -6,6 +6,22 @@
}
<p>
Le Québec possède un reseau routier impréssionant.
</p>
<div class="uk-form-horizontal uk-margin-large">
<div class="uk-margin">
<label class="uk-form-label" for="city-name">Nom de Ville</label>
<div class="uk-form-controls">
<input class="uk-input" id="city-name" type="text" name="search" placeholder="Rimouski" hx-get="/Home/CityList_Partial"
hx-trigger="keyup changed delay:500ms"
hx-target="#city-table-body"
hx-swap="innerHTML">
</div>
</div>
</div>
<table class="uk-table uk-table-striped">
<thead>
@@ -15,16 +31,10 @@
<th></th>
</tr>
</thead>
<tbody>
@foreach(var city in Model.Cities)
{
<tr>
<td>@Html.ActionLink(city.Name, "City", "Home", new {City=city.Name})</td>
<td>@((city.TotalStreetSize / 1000 ).ToString("N"))</td>
<td>@string.Join(", ", city.AvailableDates)</td>
</tr>
}
<tbody id="city-table-body"
hx-get="/Home/CityList_Partial"
hx-trigger="load"
hx-target="#city-table-body"
hx-swap="innerHTML">
</tbody>
</table>
+8 -23
View File
@@ -14,38 +14,23 @@
<body>
@* <nav class="uk-navbar-container"> *@
@* <div class="uk-container"> *@
@* <div uk-navbar> *@
@* *@
@* <div class="uk-navbar-left"> *@
@* *@
@* <a class="uk-navbar-item uk-logo" href="/" aria-label="Back to Home"> *@
@* <img src="~/images/logo.jpg" *@
@* alt="" *@
@* style="height: 1.7em;"/> *@
@* *@
@* </a> *@
@* *@
@* <ul class="uk-navbar-nav"> *@
@* <span class="uk-icon uk-margin-small-right" uk-icon="icon: star"> *@
@* L'Insoutenable Longeur Du Réseau Routier *@
@* </span> *@
@* </ul> *@
@* </div> *@
@* *@
@* </div> *@
@* </div> *@
@* </nav> *@
<br/>
<div class="uk-container">
@RenderBody()
<hr/>
<footer>
source: <a href="https://gitlab.com/all-dressed-programming/yongestreet">https://gitlab.com/all-dressed-programming/yongestreet</a>
</footer>
</div>
<script src="~/js/site.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
+1
View File
@@ -8,6 +8,7 @@
<ItemGroup>
<PackageReference Include="DuckDB.NET.Data.Full" Version="1.1.3" />
<PackageReference Include="FuzzierSharp" Version="3.0.1" />
<PackageReference Include="Htmx" Version="1.8.1" />
<PackageReference Include="Htmx.TagHelpers" Version="1.8.1" />
</ItemGroup>
+20 -20
View File
@@ -1,17 +1,14 @@
import leaflet from "https://esm.sh/leaflet@1.9.4";
import * as chartjs from "https://esm.sh/chart.js@4.4.7";
const london = [51.505, -0.09];
const centerOfTheWorld = london;
function findBaryCenter(points){
function findBaryCenter(points) {
let lat = 0;
let lon = 0;
for (const point of points){
for (const point of points) {
lat += point[0];
lon += point[1];
lon += point[1];
}
return [lat / points.length, lon / points.length]
}
@@ -19,7 +16,7 @@ function findBaryCenter(points){
function getCenter(data) {
if (!data) {
return centerOfTheWorld;
}
}
// good enough estimation
return findBaryCenter(data.map(road => findBaryCenter(road.points)));
}
@@ -34,34 +31,37 @@ function addRoads(roads, map) {
}
}
function getActiveCity(){
function getActiveCity() {
const elm = document.getElementById("leaflet-map");
return elm.getAttribute('data-city');
}
function getUrl(city){
const params = {city: city}
function getActiveDate() {
const elm = document.getElementById("leaflet-map");
return elm.getAttribute('data-date') ?? "";
}
function getUrl(city, date) {
const params = {city: city}
if (date.trim() !== "") {
params.date = date;
}
return `/api/roads?${new URLSearchParams(params).toString()}`
}
async function renderLeafletMap(city){
const resp = await fetch(getUrl(city));
async function renderLeafletMap(city, date) {
const resp = await fetch(getUrl(city, date));
const roads = await resp.json();
const map = leaflet.map("leaflet-map").setView(getCenter(roads), 13);
leaflet
.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", { maxZoom: 19 })
.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {maxZoom: 19})
.addTo(map);
addRoads(roads, map);
}
const activeCity = getActiveCity();
await Promise.all([renderLeafletMap(activeCity)])
const activeDate = getActiveDate();
await Promise.all([renderLeafletMap(activeCity, activeDate)])