Skip to content

Commit 2a8a312

Browse files
authored
Merge pull request #70 from Hoff97/develop
Improved offline support
2 parents 0cd63cc + f89e3e8 commit 2a8a312

10 files changed

Lines changed: 1480 additions & 137 deletions

File tree

backend-rust/src/bin/main.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1322,6 +1322,40 @@ async fn get_tile(s: String, z: u8, x: u32, y: u32) -> Result<(ContentType, Vec<
13221322
}
13231323
}
13241324

1325+
async fn get_cached_proxy_tile(
1326+
provider: &str,
1327+
z: u8,
1328+
x: u32,
1329+
y: u32,
1330+
extension: &str,
1331+
upstream_url: String,
1332+
content_type: ContentType,
1333+
) -> Result<(ContentType, Vec<u8>), Status> {
1334+
let path = format!("data/tiles_{provider}/{z}/{x}/{y}.{extension}");
1335+
if let Some(bytes) = load_png_from_disk(path.clone()) {
1336+
return Result::Ok((content_type, bytes));
1337+
}
1338+
1339+
let response = reqwest_client()
1340+
.get(&upstream_url)
1341+
.send()
1342+
.await
1343+
.map_err(|e| {
1344+
println!("{e}");
1345+
Status::InternalServerError
1346+
})?;
1347+
let bytes = response.bytes().await.map_err(|e| {
1348+
println!("{e}");
1349+
Status::InternalServerError
1350+
})?;
1351+
1352+
fs::create_dir_all(format!("data/tiles_{provider}/{z}/{x}"))
1353+
.map_err(|_| Status::InternalServerError)?;
1354+
fs::write(&path, &bytes).map_err(|_| Status::InternalServerError)?;
1355+
1356+
Result::Ok((content_type, bytes.to_vec()))
1357+
}
1358+
13251359
#[get("/opentopomap/<s>/<z>/<x>/<y_p>")]
13261360
async fn get_opentopomap_tile(
13271361
s: String,
@@ -1334,6 +1368,27 @@ async fn get_opentopomap_tile(
13341368
get_tile(s, z, x, y).await
13351369
}
13361370

1371+
#[get("/openstreetmap/<s>/<z>/<x>/<y_p>")]
1372+
async fn get_openstreetmap_tile(
1373+
s: String,
1374+
z: u8,
1375+
x: u32,
1376+
y_p: String,
1377+
) -> Result<(ContentType, Vec<u8>), Status> {
1378+
let y: u32 = y_p.split(".").next().unwrap().parse().unwrap();
1379+
let url = format!("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png");
1380+
get_cached_proxy_tile("openstreetmap", z, x, y, "png", url, ContentType::PNG).await
1381+
}
1382+
1383+
#[get("/satellite/<z>/<y>/<x_p>")]
1384+
async fn get_satellite_tile(z: u8, y: u32, x_p: String) -> Result<(ContentType, Vec<u8>), Status> {
1385+
let x: u32 = x_p.split(".").next().unwrap().parse().unwrap();
1386+
let url = format!(
1387+
"https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}"
1388+
);
1389+
get_cached_proxy_tile("satellite", z, x, y, "jpg", url, ContentType::JPEG).await
1390+
}
1391+
13371392
#[derive(Serialize)]
13381393
struct Stats {
13391394
webp_cache_size: usize,
@@ -1397,6 +1452,8 @@ fn rocket() -> _ {
13971452
.mount("/", routes![get_height_image])
13981453
.mount("/", routes![get_kml])
13991454
.mount("/", routes![get_opentopomap_tile])
1455+
.mount("/", routes![get_openstreetmap_tile])
1456+
.mount("/", routes![get_satellite_tile])
14001457
.mount("/", routes![get_stats])
14011458
.mount("/", routes![get_height_map])
14021459
.mount("/", routes![get_height_map_meta])

frontend/src/App.css

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,40 @@ div.restrictedSlider > .bp5-slider-track > .bp5-slider-progress:first-child {
213213
margin: 0;
214214
}
215215

216+
.offlineSelectionRow {
217+
display: flex;
218+
align-items: center;
219+
gap: 8px;
220+
margin-top: 12px;
221+
}
222+
223+
.offlineSizeEstimate {
224+
margin-top: 8px;
225+
margin-bottom: 8px;
226+
}
227+
228+
.offlineSizePlaceholder {
229+
color: #5f6b7a;
230+
}
231+
232+
.offlineLayerSelection {
233+
margin-top: 12px;
234+
}
235+
236+
.offlineLayerSelection .bp5-heading {
237+
margin-bottom: 8px;
238+
}
239+
240+
.offlineLayerSelectionGrid {
241+
display: grid;
242+
grid-template-columns: repeat(2, minmax(180px, 1fr));
243+
gap: 4px 12px;
244+
}
245+
246+
.offlineLayerLabelEstimate {
247+
color: #738091;
248+
}
249+
216250
.offlineDownloadActions {
217251
display: flex;
218252
flex-wrap: wrap;
@@ -230,4 +264,13 @@ div.restrictedSlider > .bp5-slider-track > .bp5-slider-progress:first-child {
230264
flex-direction: column;
231265
align-items: flex-start;
232266
}
267+
268+
.offlineSelectionRow {
269+
flex-direction: column;
270+
align-items: flex-start;
271+
}
272+
273+
.offlineLayerSelectionGrid {
274+
grid-template-columns: 1fr;
275+
}
233276
}

frontend/src/components/BaseLayers.tsx

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,30 @@ import { LayersControl, TileLayer, useMapEvents } from "react-leaflet";
33

44
export function BaseLayers() {
55
const [enabledBaseLayer, setEnabledBaseLayer] = useState<string>(window.localStorage.getItem("enabledBaseLayer") || "OpenTopoMap");
6+
const [isOnline, setIsOnline] = useState<boolean>(navigator.onLine);
67

78
// When the browser goes offline, switch to the proxy layer which is the
89
// only one supported for offline tile caching (same-origin, no CORS issues).
910
useEffect(() => {
11+
const updateConnectivity = () => setIsOnline(navigator.onLine);
1012
const handleOffline = () => {
11-
if (enabledBaseLayer !== "OpenTopoMap Proxy") {
13+
if (enabledBaseLayer === "OpenTopoMap") {
1214
setEnabledBaseLayer("OpenTopoMap Proxy");
1315
window.localStorage.setItem("enabledBaseLayer", "OpenTopoMap Proxy");
1416
}
1517
};
18+
window.addEventListener("online", updateConnectivity);
1619
window.addEventListener("offline", handleOffline);
20+
window.addEventListener("offline", updateConnectivity);
1721
// Also apply immediately if already offline on mount.
18-
if (!navigator.onLine && enabledBaseLayer !== "OpenTopoMap Proxy") {
22+
if (!navigator.onLine && enabledBaseLayer === "OpenTopoMap") {
1923
handleOffline();
2024
}
21-
return () => window.removeEventListener("offline", handleOffline);
25+
return () => {
26+
window.removeEventListener("online", updateConnectivity);
27+
window.removeEventListener("offline", handleOffline);
28+
window.removeEventListener("offline", updateConnectivity);
29+
};
2230
}, [enabledBaseLayer]);
2331

2432
useMapEvents({
@@ -29,6 +37,8 @@ export function BaseLayers() {
2937
});
3038

3139
const openTopoMapProxy = `${window.location.origin}/opentopomap/{s}/{z}/{x}/{y}.png`;
40+
const openStreetMapProxy = `${window.location.origin}/openstreetmap/{s}/{z}/{x}/{y}.png`;
41+
const satelliteProxy = `${window.location.origin}/satellite/{z}/{y}/{x}.jpg`;
3242

3343
return (
3444
<>
@@ -47,13 +57,13 @@ export function BaseLayers() {
4757
<LayersControl.BaseLayer checked={enabledBaseLayer === "OpenStreetMap"} name="OpenStreetMap">
4858
<TileLayer
4959
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
50-
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
60+
url={isOnline ? "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" : openStreetMapProxy}
5161
/>
5262
</LayersControl.BaseLayer>
5363
<LayersControl.BaseLayer checked={enabledBaseLayer === "Satellite"} name="Satellite">
5464
<TileLayer
5565
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
56-
url="https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}"
66+
url={isOnline ? "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}" : satelliteProxy}
5767
/>
5868
</LayersControl.BaseLayer>
5969
</>);

0 commit comments

Comments
 (0)