From 9b393962f2fc3e100383a8d1af772677f4d3e507 Mon Sep 17 00:00:00 2001 From: Thomas Buckley-Houston Date: Mon, 30 Mar 2026 16:22:23 +0100 Subject: [PATCH 1/2] Links to launch day comments around the web --- ssg/content/news/hello-world.md | 10 ++++++++++ ssg/themes/hugo-trainsh/assets/css/style.css | 5 ++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/ssg/content/news/hello-world.md b/ssg/content/news/hello-world.md index cd4d266..8839200 100644 --- a/ssg/content/news/hello-world.md +++ b/ssg/content/news/hello-world.md @@ -6,3 +6,13 @@ title = 'Launch day' Ryan and I are very proud to announce the launch of this website sharing the findings from our months of hard work. You can read more about our technical journey on our personal blogs: [Ryan](https://ryan.berge.rs/posts/total-viewshed-algorithm/) and [Tom](https://tombh.co.uk/longest-line-of-sight). We have lots more plans for the project, so watch this space! Subscribe to our [RSS feed](https://alltheviews.world/index.xml). + +## Commentary from around the web +* [Hacker News](https://news.ycombinator.com/item?id=46943568) +* [Lobster.rs](https://lobste.rs/s/8959u3) +* [r/rust](https://www.reddit.com/r/rust/comments/1r00jsi/algorithmically_finding_the_longest_line_of_sight) +* [r/geography](https://www.reddit.com/r/geography/comments/1r01raa/algorithmically_finding_the_longest_line_of_sight/) +* [r/gis](https://www.reddit.com/r/gis/comments/1r00l6e/algorithmically_finding_the_longest_line_of_sight/) +* [r/FromAfar](https://www.reddit.com/r/FromAfar/comments/1r07aka/every_longest_line_of_sight_on_earth/) +* [r/amateurradio](https://www.reddit.com/r/amateurradio/comments/1r09x0c/longest_linesofsight_in_the_world/) + diff --git a/ssg/themes/hugo-trainsh/assets/css/style.css b/ssg/themes/hugo-trainsh/assets/css/style.css index 458a259..c221c99 100644 --- a/ssg/themes/hugo-trainsh/assets/css/style.css +++ b/ssg/themes/hugo-trainsh/assets/css/style.css @@ -479,11 +479,9 @@ body > header { /* blog post list - clean grid */ ul.blog-posts { list-style-type: none; + display: inline-block; padding: 0; margin: 0; - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 2em 3em; } @media (max-width: 560px) { @@ -495,6 +493,7 @@ ul.blog-posts { ul.blog-posts li { position: relative; + margin-bottom: 2em; } ul.blog-posts li::before { From e63d1f0ddbe2f826d1c5c46e0e60fc038820eeb5 Mon Sep 17 00:00:00 2001 From: Thomas Buckley-Houston Date: Mon, 30 Mar 2026 22:20:10 +0100 Subject: [PATCH 2/2] Google Earth links --- website/src/lib/getLongestLine.ts | 29 +++++++++++++++++++++++++++ website/src/lib/renderLongestLine.ts | 27 ++++++++++++++----------- website/src/lib/utils.ts | 8 ++++++++ website/src/modals/CurrentLine.svelte | 22 +++++++++++++++++++- 4 files changed, 73 insertions(+), 13 deletions(-) diff --git a/website/src/lib/getLongestLine.ts b/website/src/lib/getLongestLine.ts index afa90bb..7fb0983 100644 --- a/website/src/lib/getLongestLine.ts +++ b/website/src/lib/getLongestLine.ts @@ -8,6 +8,7 @@ import { CDN_BUCKET, clamp, endLoadingSpinner, + getElevationAtCoord, Log, startLoadingSpinner, VERSION, @@ -22,6 +23,7 @@ export type LongestLine = { angle: number; from: LngLat; to: LngLat; + googleEarth: string; }; type IndexedTile = { @@ -123,6 +125,33 @@ async function getLongestLineCandidate(cog: GeoTIFFImage, coordinate: LngLat) { return { distance, angle } as LongestLine; } +export async function makeGoogleEarthLink(line: LongestLine) { + const base = 'https://earth.google.com/web/@'; + const latlon = `${line.from.lat},${line.from.lng}`; + + // Add a bit because sometimes we're placed under the terrain. + const extraAltitude = 10; + + const altitude = (await getElevationAtCoord(line.from)) + extraAltitude; + + // We need a little bit of distance so that the `heading` has something to point at. + const distance = 1; + + // Google Earth measures heading from North. TODO: we should too. + const heading = 90 - line.angle; + + // How wide the camera view is? + const fieldOfView = 15; + + // Whether camera is pointing up or down. 90 is just flat looking at the horizon. + const tilt = 90; + + // Looks like "CgRCAggBQgIIAEoNCP___________wEQAA", I think it's just user-specific session stuff? + const data = ``; + + return `${base}${latlon},${altitude}a,${distance}d,${fieldOfView}y,${heading}h,${tilt}t,0r/data=${data}`; +} + async function getPointFromRaster(cog: GeoTIFFImage, x: number, y: number) { // We need to find the longest line in a 5x5 grid to get around precision loss. const around = 2; diff --git a/website/src/lib/renderLongestLine.ts b/website/src/lib/renderLongestLine.ts index 4200f71..a299f5f 100644 --- a/website/src/lib/renderLongestLine.ts +++ b/website/src/lib/renderLongestLine.ts @@ -1,8 +1,10 @@ import { type GeoJSONFeature, type GeoJSONSource, LngLat } from 'maplibre-gl'; import proj4 from 'proj4'; import { navigate } from 'svelte5-router'; + import { state } from '../state.svelte.ts'; -import { getLongestLine } from './getLongestLine.ts'; +import { getLongestLine, makeGoogleEarthLink } from './getLongestLine.ts'; + import { ANGLE_SHIFT, aeqdProjectionString, @@ -58,30 +60,31 @@ function extractCoordFromURL(coordFromURL: string) { } export async function render(lngLat: LngLat) { - const longest_line = await getLongestLine(lngLat); - if (longest_line === undefined) { + const longestLine = await getLongestLine(lngLat); + if (longestLine === undefined) { return; } - state.longestLine = longest_line; + state.longestLine = longestLine; if (import.meta.env.DEV) { - console.log(longest_line); + console.log(longestLine); } - longest_line.angle = longest_line.angle + ANGLE_SHIFT; + longestLine.angle = longestLine.angle + ANGLE_SHIFT; - const θ = toRadians(longest_line.angle); - const dx = longest_line.distance * Math.cos(θ); - const dy = longest_line.distance * Math.sin(θ); + const θ = toRadians(longestLine.angle); + const dx = longestLine.distance * Math.cos(θ); + const dy = longestLine.distance * Math.sin(θ); const rotatedClockwiseAEQD = rotate(dx, dy, -0.5); const rotatedAntiAEQD = rotate(dx, dy, +0.5); const aeqd = aeqdProjectionString(lngLat.lng, lngLat.lat); const unrotated = proj4(aeqd, proj4.WGS84, [dx, dy]); - longest_line.from = lngLat; - longest_line.to = new LngLat(unrotated[0], unrotated[1]); - state.longestLine = longest_line; + longestLine.from = lngLat; + longestLine.to = new LngLat(unrotated[0], unrotated[1]); + longestLine.googleEarth = await makeGoogleEarthLink(longestLine); + state.longestLine = longestLine; const rotatedClockwiseLonLat = proj4(aeqd, proj4.WGS84, rotatedClockwiseAEQD); const rotatedAntiLonLat = proj4( diff --git a/website/src/lib/utils.ts b/website/src/lib/utils.ts index 8c13ffe..0fbdee6 100644 --- a/website/src/lib/utils.ts +++ b/website/src/lib/utils.ts @@ -194,3 +194,11 @@ export function setVectorVisibility(state: AppState, isVisible: boolean) { ); } } + +export async function getElevationAtCoord(coord: LngLat) { + const base = 'https://api.elevationapi.com/api/Elevation'; + const url = `${base}?lat=${coord.lat}&lon=${coord.lng}&dataSet=NASADEM`; + const result = await fetch(url); + const payload = await result.json(); + return payload.geoPoints[0].elevation; +} diff --git a/website/src/modals/CurrentLine.svelte b/website/src/modals/CurrentLine.svelte index 5964a72..a3eac1b 100644 --- a/website/src/modals/CurrentLine.svelte +++ b/website/src/modals/CurrentLine.svelte @@ -1,5 +1,5 @@