-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Add map #337
base: main
Are you sure you want to change the base?
feat: Add map #337
Conversation
Really naive way of doing it, no clustering. just load a 1000 thumbnails haha
32f99ab
to
b583fd5
Compare
b583fd5
to
4905e23
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really love this PR. I think it will be an awesome feature, just needs a bit more work 😄
Some(data) => match data.pos { | ||
Some(p) => { | ||
sqlx::query!( | ||
r#" | ||
INSERT OR REPLACE INTO media ( id, path, taken_at, type, latitude, longitude, walked ) | ||
VALUES ( ?1, ?2, ?3, ?4, ?5, ?6, 1 ) | ||
"#, | ||
processed_media.uuid, | ||
processed_media.path, | ||
data.taken_at, | ||
media_type, | ||
p.0, | ||
p.1, | ||
).execute(pool).await? | ||
} | ||
None => { | ||
sqlx::query!( | ||
r#" | ||
INSERT OR REPLACE INTO media ( id, path, taken_at, type, walked ) | ||
VALUES ( ?1, ?2, ?3, ?4, 1 ) | ||
"#, | ||
processed_media.uuid, | ||
processed_media.path, | ||
data.taken_at, | ||
media_type, | ||
) | ||
.execute(pool) | ||
.await? | ||
} | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some(data) => match data.pos { | |
Some(p) => { | |
sqlx::query!( | |
r#" | |
INSERT OR REPLACE INTO media ( id, path, taken_at, type, latitude, longitude, walked ) | |
VALUES ( ?1, ?2, ?3, ?4, ?5, ?6, 1 ) | |
"#, | |
processed_media.uuid, | |
processed_media.path, | |
data.taken_at, | |
media_type, | |
p.0, | |
p.1, | |
).execute(pool).await? | |
} | |
None => { | |
sqlx::query!( | |
r#" | |
INSERT OR REPLACE INTO media ( id, path, taken_at, type, walked ) | |
VALUES ( ?1, ?2, ?3, ?4, 1 ) | |
"#, | |
processed_media.uuid, | |
processed_media.path, | |
data.taken_at, | |
media_type, | |
) | |
.execute(pool) | |
.await? | |
} | |
}, | |
Some(data) => { | |
let lat = data.pos.map(|p| p.0); | |
let lon = data.pos.map(|p| p.1); | |
sqlx::query!( | |
r#" | |
INSERT OR REPLACE INTO media ( id, path, taken_at, type, latitude, longitude, walked ) | |
VALUES ( ?1, ?2, ?3, ?4, ?5, ?6, 1 ) | |
"#, | |
processed_media.uuid, | |
processed_media.path, | |
data.taken_at, | |
media_type, | |
lat, | |
lon | |
).execute(pool).await? | |
} |
#[allow(clippy::future_not_send)] | ||
pub async fn media_with_pos(pool: &SqlitePool) -> Result<Vec<MediaRow>> { | ||
let query = r" | ||
SELECT id, path, taken_at, type as media_type, archived, favorite, latitude, longitude FROM media | ||
WHERE latitude IS NOT NULL; | ||
" | ||
.to_string(); | ||
let query = sqlx::query_as::<Sqlite, MediaRow>(&query); | ||
query.fetch_all(pool).await.wrap_err("Failed to fetch rows") | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#[allow(clippy::future_not_send)] | |
pub async fn media_with_pos(pool: &SqlitePool) -> Result<Vec<MediaRow>> { | |
let query = r" | |
SELECT id, path, taken_at, type as media_type, archived, favorite, latitude, longitude FROM media | |
WHERE latitude IS NOT NULL; | |
" | |
.to_string(); | |
let query = sqlx::query_as::<Sqlite, MediaRow>(&query); | |
query.fetch_all(pool).await.wrap_err("Failed to fetch rows") | |
} | |
#[allow(clippy::future_not_send)] | |
pub async fn media_with_pos(pool: &SqlitePool, filter: impl Into<Filter>) -> Result<Vec<MediaRow>> { | |
let filter = filter.into(); | |
let query = format!( | |
r" | |
SELECT id, path, taken_at, type as media_type, archived, favorite, latitude, longitude FROM media | |
{filter} AND latitude IS NOT NULL; | |
" | |
); | |
let mut query = sqlx::query_as::<Sqlite, MediaRow>(&query); | |
query = filter.bind(query); | |
query.fetch_all(pool).await.wrap_err("Failed to fetch rows") | |
} |
Allow filtering, more on that below ....
While you are developing you can just add this implementation to call your method:
impl From<()> for Filter {
fn from(_value: ()) -> Self {
Self {
archived: false,
favorite: None,
taken_after: None,
taken_before: None,
}
}
}
...
db::media_with_pos(pool, ())
....
@@ -3,7 +3,7 @@ use color_eyre::{ | |||
eyre::{eyre, Context}, | |||
Result, | |||
}; | |||
use exif::{Exif, In, Tag, Value}; | |||
use exif::{Exif, In, Rational, Tag, Value}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use exif::{Exif, In, Rational, Tag, Value}; | |
use exif::{Exif, In, Tag, Value}; |
} | ||
|
||
pub(super) async fn render(pool: &Pool<Sqlite>, config: &Config, state: State) -> Response { | ||
let media = db::media_with_pos(pool) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let media = db::media_with_pos(pool) | |
let media = db::media_with_pos(pool, &state) |
Pass in state to filter ....
pub fn run(listener: Listener, pool: Pool<Sqlite>, config: Config) -> Result<Server> { | ||
let pool = web::Data::new(pool); | ||
let config = web::Data::new(config); | ||
|
||
let server = HttpServer::new(move || { | ||
let mut app = App::new(); | ||
let mut app = App::new().wrap(from_fn(log_errors)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, didn't realize this was a problem 🙈
<link rel="stylesheet" href="https://api.mapbox.com/mapbox-gl-js/v3.8.0/mapbox-gl.css"> | ||
|
||
<script src="https://api.mapbox.com/mapbox-gl-js/v3.8.0/mapbox-gl.js"></script> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we pull in the dependencies and bake them into the binary, similar to what I do here. I was very much trying to make an effort to have the server no depend on any external dependencies during runtime.
<!-- <div hx-get="/hx/gallery" --> | ||
<div hx-get="/hx/map" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of doing this, my idea is to keep the filter bar in the regular view, and just replace the <div class="media-gallery">
with the map. That way we can still use the filters while using the map ...
@@ -0,0 +1,139 @@ | |||
<div id="map"></div> | |||
<script> | |||
mapboxgl.accessToken = 'pk.eyJ1Ijoicm9hbGRzdG9ybSIsImEiOiJjbHdkaDhlN2QxNGFvMnFwcDB6YmFjd3RsIn0.kZAP04DN43268ORUH5bJsw'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to be parameterized, and maybe just disable the map functionality all together if the token is not supplied.
Started work on a map. I considered spatialite or running clustering in Rust but ultimately you want the map to be responsive. So clusters must appear and disappear based on zoomstate and doing backend roundtrips on those just didn't seem like a simple or sane starting point.
What this code does now is basically load every image with a location and send their url's and positions over to the frontend. Mapbox is then responsible for clustering them. When enabling clustering on a geojson source mapbox adds some properties to points that represent clusters. This can be used to render clusters in one layer with simple circles. Points that represent individual images can be displayed in another layer (a symbol layer).
One clever thingy here is that the thumbnails are not loaded up front! So I send over urls for every single image with a position but they are not loaded untill needed. And this is done by listening to mapbox's
styleimagemissing
event. When that happens we add that specific thumbnail to the map and 🎉 it works with almost no latency.Work left to do: