From a652f07b0bfffda75ba2deca729afbe2a7333c26 Mon Sep 17 00:00:00 2001 From: "Victor \"multun\" Collod" Date: Sat, 22 Jun 2024 17:15:28 +0200 Subject: [PATCH 1/3] backend, frontend: render marker as bitmap server-side --- backend/Cargo.lock | 332 ++++++++++++++++++++++ backend/Cargo.toml | 2 + backend/src/api.rs | 2 + backend/src/api/icons.rs | 241 +++++++++++++--- frontend/components/NominatimPicker.vue | 4 +- frontend/components/SingleEntityMap.vue | 11 +- frontend/components/viewer/FullResult.vue | 2 +- frontend/components/viewer/Map.vue | 6 +- frontend/components/viewer/map/Marker.vue | 66 ++--- 9 files changed, 569 insertions(+), 97 deletions(-) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index ba94ba7..2c05133 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -115,6 +115,18 @@ version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + [[package]] name = "async-trait" version = "0.1.80" @@ -401,6 +413,12 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "colorchoice" version = "1.0.1" @@ -523,6 +541,12 @@ dependencies = [ "syn 2.0.66", ] +[[package]] +name = "data-url" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" + [[package]] name = "der" version = "0.7.9" @@ -630,6 +654,15 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +[[package]] +name = "fdeflate" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" +dependencies = [ + "simd-adler32", +] + [[package]] name = "figment" version = "0.10.19" @@ -644,6 +677,22 @@ dependencies = [ "version_check", ] +[[package]] +name = "flate2" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + [[package]] name = "flume" version = "0.11.0" @@ -661,6 +710,29 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "fontconfig-parser" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a595cb550439a117696039dfc69830492058211b771a2a165379f2a1a53d84d" +dependencies = [ + "roxmltree 0.19.0", +] + +[[package]] +name = "fontdb" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e32eac81c1135c1df01d4e6d4233c47ba11f6a6d07f33e0bba09d18797077770" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2", + "slotmap", + "tinyvec", + "ttf-parser", +] + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -765,6 +837,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gif" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "gimli" version = "0.29.0" @@ -1143,6 +1225,12 @@ dependencies = [ "utf8_iter", ] +[[package]] +name = "imagesize" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284" + [[package]] name = "indexmap" version = "1.9.3" @@ -1207,6 +1295,12 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "jpeg-decoder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" + [[package]] name = "js-sys" version = "0.3.69" @@ -1231,6 +1325,16 @@ dependencies = [ "simple_asn1", ] +[[package]] +name = "kurbo" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5aa9f0f96a938266bdb12928a67169e8d22c6a786fda8ed984b85e6ba93c3c" +dependencies = [ + "arrayvec", + "smallvec", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -1322,6 +1426,15 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "memmap2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" +dependencies = [ + "libc", +] + [[package]] name = "mime" version = "0.3.17" @@ -1351,6 +1464,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" dependencies = [ "adler", + "simd-adler32", ] [[package]] @@ -1638,6 +1752,12 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + [[package]] name = "pin-project" version = "1.1.5" @@ -1697,6 +1817,19 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "png" +version = "0.17.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -1945,6 +2078,31 @@ dependencies = [ "winreg", ] +[[package]] +name = "resvg" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "944d052815156ac8fa77eaac055220e95ba0b01fa8887108ca710c03805d9051" +dependencies = [ + "gif", + "jpeg-decoder", + "log", + "pico-args", + "rgb", + "svgtypes", + "tiny-skia", + "usvg", +] + +[[package]] +name = "rgb" +version = "0.8.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05aaa8004b64fd573fc9d002f4e632d51ad4f026c2b5ba95fcb6c2f32c2c47d8" +dependencies = [ + "bytemuck", +] + [[package]] name = "ring" version = "0.17.8" @@ -1960,6 +2118,18 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "roxmltree" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f" + +[[package]] +name = "roxmltree" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" + [[package]] name = "rsa" version = "0.9.6" @@ -2082,6 +2252,22 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +[[package]] +name = "rustybuzz" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfb9cf8877777222e4a3bc7eb247e398b56baba500c38c1c46842431adc8b55c" +dependencies = [ + "bitflags 2.5.0", + "bytemuck", + "smallvec", + "ttf-parser", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", +] + [[package]] name = "ryu" version = "1.0.18" @@ -2104,12 +2290,14 @@ dependencies = [ "number_range", "rand", "reqwest", + "resvg", "scrypt", "serde", "serde_json", "serde_with", "sqlx", "time", + "tiny-skia", "tokio", "tower-http", "tracing", @@ -2297,6 +2485,12 @@ dependencies = [ "rand_core", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "simple_asn1" version = "0.6.2" @@ -2309,6 +2503,21 @@ dependencies = [ "time", ] +[[package]] +name = "simplecss" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d" +dependencies = [ + "log", +] + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "slab" version = "0.4.9" @@ -2318,6 +2527,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + [[package]] name = "smallvec" version = "1.13.2" @@ -2580,6 +2798,15 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +dependencies = [ + "float-cmp", +] + [[package]] name = "stringprep" version = "0.1.5" @@ -2603,6 +2830,16 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +[[package]] +name = "svgtypes" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fae3064df9b89391c9a76a0425a69d124aee9c5c28455204709e72c39868a43c" +dependencies = [ + "kurbo", + "siphasher", +] + [[package]] name = "syn" version = "1.0.109" @@ -2721,6 +2958,32 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "png", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + [[package]] name = "tinystr" version = "0.7.6" @@ -2966,6 +3229,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "ttf-parser" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8" + [[package]] name = "typenum" version = "1.17.0" @@ -2996,6 +3265,18 @@ version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +[[package]] +name = "unicode-bidi-mirroring" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cb788ffebc92c5948d0e997106233eeb1d8b9512f93f41651f52b6c5f5af86" + +[[package]] +name = "unicode-ccc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df77b101bcc4ea3d78dafc5ad7e4f58ceffe0b2b16bf446aeb50b6cb4157656" + [[package]] name = "unicode-ident" version = "1.0.12" @@ -3017,12 +3298,24 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" +[[package]] +name = "unicode-script" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8d71f5726e5f285a935e9fe8edfd53f0491eb6e9a5774097fdabee7cd8c9cd" + [[package]] name = "unicode-segmentation" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +[[package]] +name = "unicode-vo" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" + [[package]] name = "unicode_categories" version = "0.1.1" @@ -3052,6 +3345,33 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" +[[package]] +name = "usvg" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b84ea542ae85c715f07b082438a4231c3760539d902e11d093847a0b22963032" +dependencies = [ + "base64 0.22.1", + "data-url", + "flate2", + "fontdb", + "imagesize", + "kurbo", + "log", + "pico-args", + "roxmltree 0.20.0", + "rustybuzz", + "simplecss", + "siphasher", + "strict-num", + "svgtypes", + "tiny-skia-path", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", +] + [[package]] name = "utf16_iter" version = "1.0.5" @@ -3236,6 +3556,12 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "weezl" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + [[package]] name = "whoami" version = "1.5.1" @@ -3447,6 +3773,12 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + [[package]] name = "yansi" version = "1.0.1" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 9601eda..b5c4840 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -39,3 +39,5 @@ reqwest = { version = "0.12", features = [ "json", "rustls-tls", ], default-features = false } +tiny-skia = { version = "0.11.4", features = ["png-format"] } +resvg = "0.42.0" \ No newline at end of file diff --git a/backend/src/api.rs b/backend/src/api.rs index 87225fc..841f23e 100644 --- a/backend/src/api.rs +++ b/backend/src/api.rs @@ -201,6 +201,7 @@ pub enum AppError { Validation(String), Database(sqlx::Error), InvalidPagination, + Internal(Option), } #[derive(FromRequest)] @@ -256,6 +257,7 @@ impl IntoResponse for AppError { }, AppError::Unauthorized => (StatusCode::UNAUTHORIZED, "unauthorized", None), AppError::InvalidPagination => (StatusCode::BAD_REQUEST, "invalid_pagination", None), + AppError::Internal(e) => (StatusCode::INTERNAL_SERVER_ERROR, "internal_error", e), }; let resp = ( diff --git a/backend/src/api/icons.rs b/backend/src/api/icons.rs index fbba8d5..040c7ff 100644 --- a/backend/src/api/icons.rs +++ b/backend/src/api/icons.rs @@ -1,61 +1,218 @@ +use std::sync::Arc; + use crate::{api::AppState, models::icon::Icon}; use axum::{ - extract::{Path, State}, + extract::{Path, Query, State}, http::StatusCode, response::{IntoResponse, Response}, routing::{get, Router}, - Json, }; -use serde_json::json; +use resvg::{tiny_skia, usvg, usvg::ImageHrefResolver}; +use serde::Deserialize; +use usvg::ImageKind::{JPEG, PNG, SVG}; + +use sqlx::{pool::PoolConnection, Postgres}; +use tiny_skia::{Pixmap, Transform}; +use usvg::{Options, Tree}; -use super::DbConn; +use super::{AppError, DbConn}; pub fn routes() -> Router { - Router::new().route("/:hash", get(get_icon)) + Router::new() + .route("/:hash", get(get_icon)) + .route("/pin", get(get_pin)) +} + +async fn get_icon_internal( + state: &AppState, + hash: String, + conn: &mut PoolConnection, +) -> Result<(Vec, String), AppError> { + // Check cache firsts + if let Some(icon) = state.icon_cache.read().await.get(&hash) { + return Ok(icon.clone()); + } + + // If not found in cache, query the database + let icon = Icon::get(hash.clone(), conn).await?; + let icon = (icon.data, icon.http_mime_type); + + // Update cache + state.icon_cache.write().await.insert(hash, icon.clone()); + + Ok(icon) } async fn get_icon( State(state): State, DbConn(mut conn): DbConn, Path(hash): Path, -) -> Response { - // Check cache firsts - if let Some(icon) = state.icon_cache.read().await.get(&hash) { - return ( - StatusCode::OK, - [ - ("Content-Type", icon.1.clone()), - ("Cache-Control", "public, max-age=31536000".to_owned()), - ], - icon.0.clone(), - ) - .into_response(); +) -> Result { + let (data, mime_type) = get_icon_internal(&state, hash, &mut conn).await?; + + Ok(( + StatusCode::OK, + [ + ("Content-Type", mime_type), + ("Cache-Control", "public, max-age=31536000".to_owned()), + ], + data, + ) + .into_response()) +} + +#[derive(Deserialize, Debug)] +pub struct RenderQuery { + pub h: Option, + pub w: Option, + pub bc: Option, + pub fc: Option, + pub ih: Option, +} + +async fn get_pin( + State(state): State, + DbConn(mut conn): DbConn, + Query(query): Query, +) -> Result { + let (border_color, fill_color, icon_hash) = ( + format!("#{}", query.bc.as_deref().unwrap_or("222222")), + format!("#{}", query.fc.as_deref().unwrap_or("9999FF")), + query.ih, + ); + + let icon = match icon_hash { + None => None, + Some(icon_hash) => Some(get_icon_internal(&state, icon_hash, &mut conn).await?), + }; + + if query.h > Some(100) || query.w > Some(100) { + return Err(AppError::Validation("invalid_size".to_string())); } - // If not found in cache, query the database - match Icon::get(hash.clone(), &mut conn).await { - Ok(icon) => { - // Update cache - state - .icon_cache - .write() - .await - .insert(hash, (icon.data.clone(), icon.http_mime_type.clone())); - - ( - StatusCode::OK, - [ - ("Content-Type", icon.http_mime_type.clone()), - ("Cache-Control", "public, max-age=31536000".to_owned()), - ], - icon.data, - ) - .into_response() + let icon_svg = match icon.as_ref() { + None => "".to_owned(), + Some(_) => r#" + + "# + .to_owned(), + }; + let pin_svg = format!( + r#" + + + {icon_svg} + + "# + ); + + let mut opt = Options::default(); + + // We know that our SVG won't have DataUrl hrefs, just return None for such case. + let resolve_data = Box::new(|_: &str, _: std::sync::Arc>, _: &usvg::Options| None); + + let parsed_image = if let Some((data, mime)) = icon { + match mime.as_str() { + "image/png" => Some(PNG(Arc::new(data.clone()))), + "image/jpeg" => Some(JPEG(Arc::new(data.clone()))), + "image/svg+xml" => { + let tree: Tree = match Tree::from_data(&data, &Options::default()) { + Ok(tree) => tree, + Err(_) => { + return Err(AppError::Internal(Some("svg_renderer_failed".to_string()))) + } + }; + Some(SVG(tree)) + } + _ => None, } - Err(_) => ( - StatusCode::NOT_FOUND, - Json(json!({"error": "Icon not found"})), - ) - .into_response(), - } + } else { + None + }; + + let resolve_string = Box::new(move |href: &str, _: &Options| { + if let Some(parsed_image) = parsed_image.as_ref() { + match href { + "icon" => Some(parsed_image.clone()), + _ => None, + } + } else { + None + } + }); + + // Assign new ImageHrefResolver option using our closures. + opt.image_href_resolver = ImageHrefResolver { + resolve_data, + resolve_string, + }; + + let tree = match Tree::from_str(&pin_svg, &opt) { + Ok(tree) => tree, + Err(_) => return Err(AppError::Internal(Some("svg_renderer_failed".to_string()))), + }; + + let pixmap_size = tree.size().to_int_size(); + + let (height, width, scale) = match (query.h, query.w) { + (Some(h), Some(w)) => (h, w, None), + (Some(h), None) => { + let scale = h as f32 / pixmap_size.height() as f32; + (h, (pixmap_size.width() as f32 * scale) as u32, Some(scale)) + } + (None, Some(w)) => { + let scale = w as f32 / pixmap_size.width() as f32; + ((pixmap_size.height() as f32 * scale) as u32, w, Some(scale)) + } + _ => (38, 24, None), + }; + + let scale_x = scale.unwrap_or(width as f32 / pixmap_size.width() as f32); + let scale_y = scale.unwrap_or(height as f32 / pixmap_size.height() as f32); + let transform = Transform::from_scale(scale_x, scale_y); + + let mut pixmap = match Pixmap::new(width, height) { + Some(pixmap) => pixmap, + None => { + return Err(AppError::Internal(Some( + "pixmap_creation_failed".to_string(), + ))) + } + }; + + resvg::render(&tree, transform, &mut pixmap.as_mut()); + let image_data = match pixmap.encode_png() { + Ok(image_data) => image_data, + Err(_) => return Err(AppError::Internal(Some("png_encoding_failed".to_string()))), + }; + + Ok(( + StatusCode::OK, + [ + ("Content-Type", "image/png"), + ("Cache-Control", "public, max-age=31536000"), + ], + image_data, + ) + .into_response()) } diff --git a/frontend/components/NominatimPicker.vue b/frontend/components/NominatimPicker.vue index 75b2923..6387af4 100644 --- a/frontend/components/NominatimPicker.vue +++ b/frontend/components/NominatimPicker.vue @@ -30,9 +30,7 @@ class="!h-80" :coordinates="transformedCoordinates" :locked="false" - fill-color="#9999FF" - border-color="#222222" - :icon-hash="null" + category-id="default" :zoom="10" /> Addresse : {{ results[0].display_name }} diff --git a/frontend/components/SingleEntityMap.vue b/frontend/components/SingleEntityMap.vue index d01ceb7..1ea76fa 100644 --- a/frontend/components/SingleEntityMap.vue +++ b/frontend/components/SingleEntityMap.vue @@ -28,10 +28,7 @@ :callback-item="null" :width="24" :height="38" - :fill="props.fillColor" - :stroke="props.borderColor" :highlighted="false" - :icon-url="iconUrl" /> @@ -43,16 +40,10 @@ import type { Coordinate } from 'ol/coordinate' const props = defineProps<{ coordinates: Coordinate - fillColor: string - borderColor: string - iconHash: string | null | undefined + categoryId: string zoom: number locked: boolean }>() - -const iconUrl = computed(() => { - return props.iconHash ? `/api/icons/${props.iconHash}` : null -}) From 0b2b6e6bb20e19a5941195c5c1690bfae6d94f2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89lys=C3=A6?= <101974839+ElysaSrc@users.noreply.github.com> Date: Sat, 22 Jun 2024 21:16:53 +0200 Subject: [PATCH 2/3] backend, frontend: customizable tiles url --- backend/src/models/options.rs | 12 ++++++ frontend/components/viewer/EntityAddForm.vue | 1 - frontend/components/viewer/Map.vue | 34 ++++++++++++++++- frontend/composables/useDarkMode.ts | 39 +++++++++++--------- frontend/lib/viewer-state.ts | 13 +++++++ frontend/openapi.json | 20 ++++++++++ frontend/package-lock.json | 22 ++++------- frontend/package.json | 4 +- frontend/pages/admin/config.vue | 28 ++++++++++++++ frontend/pages/admin/users/index.vue | 2 +- 10 files changed, 137 insertions(+), 38 deletions(-) diff --git a/backend/src/models/options.rs b/backend/src/models/options.rs index 298462b..06cc0c4 100644 --- a/backend/src/models/options.rs +++ b/backend/src/models/options.rs @@ -107,6 +107,14 @@ pub struct CartographyInitConfig { pub center_lng: f64, /// Zoom level of the map (from 2 to 20) pub zoom: u8, + /// Light mode map url + pub light_map_url: String, + /// Dark mode map url + pub dark_map_url: String, + /// Light mode map attributions + pub light_map_attributions: String, + /// Dark mode map attributions + pub dark_map_attributions: String, } impl OptionConfig for CartographyInitConfig { @@ -121,6 +129,10 @@ impl Default for CartographyInitConfig { center_lat: 47.0, center_lng: 2.0, zoom: 5, + light_map_url: "https://tile.openstreetmap.org/{z}/{x}/{y}.png".to_string(), + light_map_attributions: "Map data © OpenStreetMap contributors".to_string(), + dark_map_url: "https://tile.openstreetmap.org/{z}/{x}/{y}.png".to_string(), + dark_map_attributions: "Map data © OpenStreetMap contributors".to_string(), } } } diff --git a/frontend/components/viewer/EntityAddForm.vue b/frontend/components/viewer/EntityAddForm.vue index f1c80d9..c18a98e 100644 --- a/frontend/components/viewer/EntityAddForm.vue +++ b/frontend/components/viewer/EntityAddForm.vue @@ -199,7 +199,6 @@ const commentFieldValid = ref( ) function reset_refs() { - console.log('hein pq fail le captcha switch la famille') editedEntity.value = { category_id: '', data: {}, diff --git a/frontend/components/viewer/Map.vue b/frontend/components/viewer/Map.vue index 132e2c3..90a2b2a 100644 --- a/frontend/components/viewer/Map.vue +++ b/frontend/components/viewer/Map.vue @@ -15,7 +15,11 @@ /> - + { + tileUrl.value = darkMode.isDark.value + ? state.mapSource.dark.url + : state.mapSource.light.url + + tileAttr.value = darkMode.isDark.value + ? state.mapSource.dark.attribution + : state.mapSource.light.attribution + }, +) const props = defineProps<{ center: Coordinate @@ -156,7 +186,7 @@ async function handleEntityClick(entity: DisplayableCachedEntity) { } -