mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-08-31 23:53:25 +00:00
rework map bounds logic when opening files from url
This commit is contained in:
@@ -180,7 +180,7 @@ function dexieGPXFileStore(id: string): Readable<GPXFileWithStatistics> & { dest
|
|||||||
|
|
||||||
let statistics = new GPXStatisticsTree(gpx);
|
let statistics = new GPXStatisticsTree(gpx);
|
||||||
if (!fileState.has(id)) { // Update the map bounds for new files
|
if (!fileState.has(id)) { // Update the map bounds for new files
|
||||||
updateTargetMapBounds(statistics.getStatisticsFor(new ListFileItem(id)).global.bounds);
|
updateTargetMapBounds(id, statistics.getStatisticsFor(new ListFileItem(id)).global.bounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
fileState.set(id, gpx);
|
fileState.set(id, gpx);
|
||||||
@@ -287,12 +287,13 @@ export const fileObservers: Writable<Map<string, Readable<GPXFileWithStatistics
|
|||||||
const fileState: Map<string, GPXFile> = new Map(); // Used to generate patches
|
const fileState: Map<string, GPXFile> = new Map(); // Used to generate patches
|
||||||
|
|
||||||
// Observe the file ids in the database, and maintain a map of file observers for the corresponding files
|
// Observe the file ids in the database, and maintain a map of file observers for the corresponding files
|
||||||
export function observeFilesFromDatabase() {
|
export function observeFilesFromDatabase(fitBounds: boolean) {
|
||||||
let initialize = true;
|
let initialize = true;
|
||||||
liveQuery(() => db.fileids.toArray()).subscribe(dbFileIds => {
|
liveQuery(() => db.fileids.toArray()).subscribe(dbFileIds => {
|
||||||
if (initialize) {
|
if (initialize) {
|
||||||
if (dbFileIds.length > 0) {
|
console.log('fitBounds', fitBounds);
|
||||||
initTargetMapBounds(dbFileIds.length);
|
if (fitBounds && dbFileIds.length > 0) {
|
||||||
|
initTargetMapBounds(dbFileIds);
|
||||||
}
|
}
|
||||||
initialize = false;
|
initialize = false;
|
||||||
}
|
}
|
||||||
@@ -453,13 +454,14 @@ export const dbUtils = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
addMultiple: (files: GPXFile[]) => {
|
addMultiple: (files: GPXFile[]) => {
|
||||||
return applyGlobal((draft) => {
|
let ids = getFileIds(files.length);
|
||||||
let ids = getFileIds(files.length);
|
applyGlobal((draft) => {
|
||||||
files.forEach((file, index) => {
|
files.forEach((file, index) => {
|
||||||
file._data.id = ids[index];
|
file._data.id = ids[index];
|
||||||
draft.set(file._data.id, freeze(file));
|
draft.set(file._data.id, freeze(file));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
return ids;
|
||||||
},
|
},
|
||||||
applyToFile: (id: string, callback: (file: WritableDraft<GPXFile>) => void) => {
|
applyToFile: (id: string, callback: (file: WritableDraft<GPXFile>) => void) => {
|
||||||
applyToFiles([id], callback);
|
applyToFiles([id], callback);
|
||||||
|
@@ -84,16 +84,20 @@ gpxStatistics.subscribe(() => {
|
|||||||
slicedGPXStatistics.set(undefined);
|
slicedGPXStatistics.set(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
const targetMapBounds = writable({
|
const targetMapBounds = writable<{
|
||||||
|
bounds: mapboxgl.LngLatBounds;
|
||||||
|
ids: string[];
|
||||||
|
total: number;
|
||||||
|
}>({
|
||||||
bounds: new mapboxgl.LngLatBounds([180, 90, -180, -90]),
|
bounds: new mapboxgl.LngLatBounds([180, 90, -180, -90]),
|
||||||
count: 0,
|
ids: [],
|
||||||
total: -1
|
total: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
derived([targetMapBounds, map], (x) => x).subscribe(([bounds, $map]) => {
|
derived([targetMapBounds, map], (x) => x).subscribe(([bounds, $map]) => {
|
||||||
if (
|
if (
|
||||||
$map === null ||
|
$map === null ||
|
||||||
bounds.count !== bounds.total ||
|
bounds.ids.length > 0 ||
|
||||||
(bounds.bounds.getSouth() === 90 &&
|
(bounds.bounds.getSouth() === 90 &&
|
||||||
bounds.bounds.getWest() === 180 &&
|
bounds.bounds.getWest() === 180 &&
|
||||||
bounds.bounds.getNorth() === -90 &&
|
bounds.bounds.getNorth() === -90 &&
|
||||||
@@ -102,10 +106,13 @@ derived([targetMapBounds, map], (x) => x).subscribe(([bounds, $map]) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let currentZoom = $map.getZoom();
|
||||||
let currentBounds = $map.getBounds();
|
let currentBounds = $map.getBounds();
|
||||||
if (bounds.count !== get(fileObservers).size && currentBounds) {
|
if (bounds.total !== get(fileObservers).size &&
|
||||||
|
currentBounds &&
|
||||||
|
currentZoom > 2 // Extend current bounds only if the map is zoomed in
|
||||||
|
) {
|
||||||
// There are other files on the map
|
// There are other files on the map
|
||||||
|
|
||||||
if (
|
if (
|
||||||
currentBounds.contains(bounds.bounds.getSouthEast()) &&
|
currentBounds.contains(bounds.bounds.getSouthEast()) &&
|
||||||
currentBounds.contains(bounds.bounds.getNorthWest())
|
currentBounds.contains(bounds.bounds.getNorthWest())
|
||||||
@@ -120,33 +127,32 @@ derived([targetMapBounds, map], (x) => x).subscribe(([bounds, $map]) => {
|
|||||||
$map.fitBounds(bounds.bounds, { padding: 80, linear: true, easing: () => 1 });
|
$map.fitBounds(bounds.bounds, { padding: 80, linear: true, easing: () => 1 });
|
||||||
});
|
});
|
||||||
|
|
||||||
export function initTargetMapBounds(total: number) {
|
export function initTargetMapBounds(ids: string[]) {
|
||||||
targetMapBounds.set({
|
targetMapBounds.set({
|
||||||
bounds: new mapboxgl.LngLatBounds([180, 90, -180, -90]),
|
bounds: new mapboxgl.LngLatBounds([180, 90, -180, -90]),
|
||||||
count: 0,
|
ids,
|
||||||
total: total
|
total: ids.length,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateTargetMapBounds(bounds: { southWest: Coordinates; northEast: Coordinates }) {
|
export function updateTargetMapBounds(id: string, bounds: { southWest: Coordinates; northEast: Coordinates }) {
|
||||||
if (
|
if (get(targetMapBounds).ids.indexOf(id) === -1) {
|
||||||
bounds.southWest.lat == 90 &&
|
|
||||||
bounds.southWest.lon == 180 &&
|
|
||||||
bounds.northEast.lat == -90 &&
|
|
||||||
bounds.northEast.lon == -180
|
|
||||||
) {
|
|
||||||
// Avoid update for empty (new) files
|
|
||||||
targetMapBounds.update((target) => {
|
|
||||||
target.count += 1;
|
|
||||||
return target;
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
targetMapBounds.update((target) => {
|
targetMapBounds.update((target) => {
|
||||||
target.bounds.extend(bounds.southWest);
|
target.ids = target.ids.filter((x) => x !== id);
|
||||||
target.bounds.extend(bounds.northEast);
|
if (
|
||||||
target.count += 1;
|
bounds.southWest.lat !== 90 ||
|
||||||
|
bounds.southWest.lon !== 180 ||
|
||||||
|
bounds.northEast.lat !== -90 ||
|
||||||
|
bounds.northEast.lon !== -180
|
||||||
|
) {
|
||||||
|
// Avoid update for empty (new) files
|
||||||
|
target.bounds.extend(bounds.southWest);
|
||||||
|
target.bounds.extend(bounds.northEast);
|
||||||
|
}
|
||||||
|
|
||||||
return target;
|
return target;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -246,7 +252,7 @@ export function triggerFileInput() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function loadFiles(list: FileList | File[]) {
|
export async function loadFiles(list: FileList | File[]) {
|
||||||
let files = [];
|
let files: GPXFile[] = [];
|
||||||
for (let i = 0; i < list.length; i++) {
|
for (let i = 0; i < list.length; i++) {
|
||||||
let file = await loadFile(list[i]);
|
let file = await loadFile(list[i]);
|
||||||
if (file) {
|
if (file) {
|
||||||
@@ -254,11 +260,10 @@ export async function loadFiles(list: FileList | File[]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
initTargetMapBounds(list.length);
|
let ids = dbUtils.addMultiple(files);
|
||||||
|
|
||||||
dbUtils.addMultiple(files);
|
initTargetMapBounds(ids);
|
||||||
|
selectFileWhenLoaded(ids[0]);
|
||||||
selectFileWhenLoaded(files[0]._data.id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadFile(file: File): Promise<GPXFile | null> {
|
export async function loadFile(file: File): Promise<GPXFile | null> {
|
||||||
|
@@ -1,127 +1,122 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import GPXLayers from '$lib/components/gpx-layer/GPXLayers.svelte';
|
import GPXLayers from '$lib/components/gpx-layer/GPXLayers.svelte';
|
||||||
import ElevationProfile from '$lib/components/ElevationProfile.svelte';
|
import ElevationProfile from '$lib/components/ElevationProfile.svelte';
|
||||||
import FileList from '$lib/components/file-list/FileList.svelte';
|
import FileList from '$lib/components/file-list/FileList.svelte';
|
||||||
import GPXStatistics from '$lib/components/GPXStatistics.svelte';
|
import GPXStatistics from '$lib/components/GPXStatistics.svelte';
|
||||||
import Map from '$lib/components/Map.svelte';
|
import Map from '$lib/components/Map.svelte';
|
||||||
import Menu from '$lib/components/Menu.svelte';
|
import Menu from '$lib/components/Menu.svelte';
|
||||||
import Toolbar from '$lib/components/toolbar/Toolbar.svelte';
|
import Toolbar from '$lib/components/toolbar/Toolbar.svelte';
|
||||||
import StreetViewControl from '$lib/components/street-view-control/StreetViewControl.svelte';
|
import StreetViewControl from '$lib/components/street-view-control/StreetViewControl.svelte';
|
||||||
import LayerControl from '$lib/components/layer-control/LayerControl.svelte';
|
import LayerControl from '$lib/components/layer-control/LayerControl.svelte';
|
||||||
import Resizer from '$lib/components/Resizer.svelte';
|
import Resizer from '$lib/components/Resizer.svelte';
|
||||||
import { Toaster } from '$lib/components/ui/sonner';
|
import { Toaster } from '$lib/components/ui/sonner';
|
||||||
|
|
||||||
import { observeFilesFromDatabase, settings } from '$lib/db';
|
import { observeFilesFromDatabase, settings } from '$lib/db';
|
||||||
import { gpxStatistics, loadFiles, slicedGPXStatistics } from '$lib/stores';
|
import { gpxStatistics, loadFiles, slicedGPXStatistics } from '$lib/stores';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { languages } from '$lib/languages';
|
import { languages } from '$lib/languages';
|
||||||
import { getURLForLanguage } from '$lib/utils';
|
import { getURLForLanguage } from '$lib/utils';
|
||||||
import { getURLForGoogleDriveFile } from '$lib/components/embedding/Embedding';
|
import { getURLForGoogleDriveFile } from '$lib/components/embedding/Embedding';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
verticalFileView,
|
verticalFileView,
|
||||||
elevationProfile,
|
elevationProfile,
|
||||||
bottomPanelSize,
|
bottomPanelSize,
|
||||||
rightPanelSize,
|
rightPanelSize,
|
||||||
additionalDatasets,
|
additionalDatasets,
|
||||||
elevationFill
|
elevationFill
|
||||||
} = settings;
|
} = settings;
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
observeFilesFromDatabase();
|
let files: string[] = JSON.parse($page.url.searchParams.get('files') || '[]');
|
||||||
|
let ids: string[] = JSON.parse($page.url.searchParams.get('ids') || '[]');
|
||||||
|
let urls: string[] = files.concat(ids.map(getURLForGoogleDriveFile));
|
||||||
|
|
||||||
let files: string[] = JSON.parse($page.url.searchParams.get('files') || '[]');
|
observeFilesFromDatabase(urls.length === 0);
|
||||||
let ids: string[] = JSON.parse($page.url.searchParams.get('ids') || '[]');
|
|
||||||
let urls: string[] = files.concat(ids.map(getURLForGoogleDriveFile));
|
|
||||||
|
|
||||||
if (urls.length > 0) {
|
if (urls.length > 0) {
|
||||||
let downloads: Promise<File | null>[] = [];
|
let downloads: Promise<File | null>[] = [];
|
||||||
urls.forEach((url) => {
|
urls.forEach((url) => {
|
||||||
downloads.push(
|
downloads.push(
|
||||||
fetch(url)
|
fetch(url)
|
||||||
.then((response) => response.blob())
|
.then((response) => response.blob())
|
||||||
.then((blob) => new File([blob], url.split('/').pop()))
|
.then((blob) => new File([blob], url.split('/').pop()))
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
Promise.all(downloads).then((files) => {
|
Promise.all(downloads).then((files) => {
|
||||||
files = files.filter((file) => file !== null);
|
files = files.filter((file) => file !== null);
|
||||||
loadFiles(files);
|
loadFiles(files);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="fixed flex flex-row w-screen h-screen supports-dvh:h-dvh">
|
<div class="fixed flex flex-row w-screen h-screen supports-dvh:h-dvh">
|
||||||
<div class="flex flex-col grow h-full min-w-0">
|
<div class="flex flex-col grow h-full min-w-0">
|
||||||
<div class="grow relative">
|
<div class="grow relative">
|
||||||
<Menu />
|
<Menu />
|
||||||
<div
|
<div
|
||||||
class="absolute top-0 bottom-0 left-0 z-40 flex flex-col justify-center pointer-events-none"
|
class="absolute top-0 bottom-0 left-0 z-40 flex flex-col justify-center pointer-events-none"
|
||||||
>
|
>
|
||||||
<Toolbar />
|
<Toolbar />
|
||||||
</div>
|
</div>
|
||||||
<Map class="h-full {$verticalFileView ? '' : 'horizontal'}" />
|
<Map class="h-full {$verticalFileView ? '' : 'horizontal'}" />
|
||||||
<StreetViewControl />
|
<StreetViewControl />
|
||||||
<LayerControl />
|
<LayerControl />
|
||||||
<GPXLayers />
|
<GPXLayers />
|
||||||
<Toaster richColors />
|
<Toaster richColors />
|
||||||
{#if !$verticalFileView}
|
{#if !$verticalFileView}
|
||||||
<div class="h-10 -translate-y-10 w-full pointer-events-none absolute z-30">
|
<div class="h-10 -translate-y-10 w-full pointer-events-none absolute z-30">
|
||||||
<FileList orientation="horizontal" />
|
<FileList orientation="horizontal" />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if $elevationProfile}
|
{#if $elevationProfile}
|
||||||
<Resizer
|
<Resizer orientation="row" bind:after={$bottomPanelSize} minAfter={100} maxAfter={300} />
|
||||||
orientation="row"
|
{/if}
|
||||||
bind:after={$bottomPanelSize}
|
<div
|
||||||
minAfter={100}
|
class="{$elevationProfile ? '' : 'h-10'} flex flex-row gap-2 px-2 sm:px-4"
|
||||||
maxAfter={300}
|
style={$elevationProfile ? `height: ${$bottomPanelSize}px` : ''}
|
||||||
/>
|
>
|
||||||
{/if}
|
<GPXStatistics
|
||||||
<div
|
{gpxStatistics}
|
||||||
class="{$elevationProfile ? '' : 'h-10'} flex flex-row gap-2 px-2 sm:px-4"
|
{slicedGPXStatistics}
|
||||||
style={$elevationProfile ? `height: ${$bottomPanelSize}px` : ''}
|
panelSize={$bottomPanelSize}
|
||||||
>
|
orientation={$elevationProfile ? 'vertical' : 'horizontal'}
|
||||||
<GPXStatistics
|
/>
|
||||||
{gpxStatistics}
|
{#if $elevationProfile}
|
||||||
{slicedGPXStatistics}
|
<ElevationProfile
|
||||||
panelSize={$bottomPanelSize}
|
{gpxStatistics}
|
||||||
orientation={$elevationProfile ? 'vertical' : 'horizontal'}
|
{slicedGPXStatistics}
|
||||||
/>
|
bind:additionalDatasets={$additionalDatasets}
|
||||||
{#if $elevationProfile}
|
bind:elevationFill={$elevationFill}
|
||||||
<ElevationProfile
|
panelSize={$bottomPanelSize}
|
||||||
{gpxStatistics}
|
class="py-2"
|
||||||
{slicedGPXStatistics}
|
/>
|
||||||
bind:additionalDatasets={$additionalDatasets}
|
{/if}
|
||||||
bind:elevationFill={$elevationFill}
|
</div>
|
||||||
panelSize={$bottomPanelSize}
|
</div>
|
||||||
class="py-2"
|
{#if $verticalFileView}
|
||||||
/>
|
<Resizer orientation="col" bind:after={$rightPanelSize} minAfter={100} maxAfter={400} />
|
||||||
{/if}
|
<FileList orientation="vertical" recursive={true} style="width: {$rightPanelSize}px" />
|
||||||
</div>
|
{/if}
|
||||||
</div>
|
|
||||||
{#if $verticalFileView}
|
|
||||||
<Resizer orientation="col" bind:after={$rightPanelSize} minAfter={100} maxAfter={400} />
|
|
||||||
<FileList orientation="vertical" recursive={true} style="width: {$rightPanelSize}px" />
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- hidden links for svelte crawling -->
|
<!-- hidden links for svelte crawling -->
|
||||||
<div class="hidden">
|
<div class="hidden">
|
||||||
{#each Object.entries(languages) as [lang, label]}
|
{#each Object.entries(languages) as [lang, label]}
|
||||||
<a href={getURLForLanguage(lang, '/embed')}>
|
<a href={getURLForLanguage(lang, '/embed')}>
|
||||||
{label}
|
{label}
|
||||||
</a>
|
</a>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="postcss">
|
<style lang="postcss">
|
||||||
div :global(.toaster.group) {
|
div :global(.toaster.group) {
|
||||||
@apply absolute;
|
@apply absolute;
|
||||||
@apply right-2;
|
@apply right-2;
|
||||||
--offset: 50px !important;
|
--offset: 50px !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
Reference in New Issue
Block a user