mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2025-07-31 02:39:38 +02:00
feat: add GPX file handling and GeoJSON integration in adventure page; update dependencies and AttachmentCard component
This commit is contained in:
parent
30c58ca118
commit
64d2bdebce
6 changed files with 131 additions and 5 deletions
|
@ -2,10 +2,8 @@ from rest_framework import viewsets, status
|
|||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from django.db.models import Q
|
||||
from adventures.models import Adventure, Attachment
|
||||
from adventures.serializers import AttachmentSerializer
|
||||
import uuid
|
||||
|
||||
class AttachmentViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = AttachmentSerializer
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
"type": "module",
|
||||
"dependencies": {
|
||||
"@lukulent/svelte-umami": "^0.0.3",
|
||||
"@mapbox/togeojson": "^0.16.2",
|
||||
"emoji-picker-element": "^1.26.0",
|
||||
"gsap": "^3.12.7",
|
||||
"marked": "^15.0.4",
|
||||
|
|
40
frontend/pnpm-lock.yaml
generated
40
frontend/pnpm-lock.yaml
generated
|
@ -11,6 +11,9 @@ importers:
|
|||
'@lukulent/svelte-umami':
|
||||
specifier: ^0.0.3
|
||||
version: 0.0.3(svelte@4.2.19)
|
||||
'@mapbox/togeojson':
|
||||
specifier: ^0.16.2
|
||||
version: 0.16.2
|
||||
emoji-picker-element:
|
||||
specifier: ^1.26.0
|
||||
version: 1.26.0
|
||||
|
@ -483,6 +486,10 @@ packages:
|
|||
'@mapbox/tiny-sdf@2.0.6':
|
||||
resolution: {integrity: sha512-qMqa27TLw+ZQz5Jk+RcwZGH7BQf5G/TrutJhspsca/3SHwmgKQ1iq+d3Jxz5oysPVYTGP6aXxCo5Lk9Er6YBAA==}
|
||||
|
||||
'@mapbox/togeojson@0.16.2':
|
||||
resolution: {integrity: sha512-DcApudmw4g/grOrpM5gYPZfts6Kr8litBESN6n/27sDsjR2f+iJhx4BA0J2B+XrLlnHyJkKztYApe6oCUZpzFA==}
|
||||
hasBin: true
|
||||
|
||||
'@mapbox/unitbezier@0.0.1':
|
||||
resolution: {integrity: sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==}
|
||||
|
||||
|
@ -868,6 +875,10 @@ packages:
|
|||
engines: {node: '>=16'}
|
||||
hasBin: true
|
||||
|
||||
'@xmldom/xmldom@0.8.10':
|
||||
resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
||||
abbrev@1.1.1:
|
||||
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
|
||||
|
||||
|
@ -972,6 +983,9 @@ packages:
|
|||
resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
|
||||
buffer-from@1.1.2:
|
||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||
|
||||
builtin-modules@3.3.0:
|
||||
resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==}
|
||||
engines: {node: '>=6'}
|
||||
|
@ -1039,6 +1053,10 @@ packages:
|
|||
concat-map@0.0.1:
|
||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||
|
||||
concat-stream@2.0.0:
|
||||
resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==}
|
||||
engines: {'0': node >= 6.0}
|
||||
|
||||
confbox@0.1.7:
|
||||
resolution: {integrity: sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==}
|
||||
|
||||
|
@ -2116,6 +2134,9 @@ packages:
|
|||
type@2.7.3:
|
||||
resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==}
|
||||
|
||||
typedarray@0.0.6:
|
||||
resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==}
|
||||
|
||||
typescript@5.5.2:
|
||||
resolution: {integrity: sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==}
|
||||
engines: {node: '>=14.17'}
|
||||
|
@ -2553,6 +2574,12 @@ snapshots:
|
|||
|
||||
'@mapbox/tiny-sdf@2.0.6': {}
|
||||
|
||||
'@mapbox/togeojson@0.16.2':
|
||||
dependencies:
|
||||
'@xmldom/xmldom': 0.8.10
|
||||
concat-stream: 2.0.0
|
||||
minimist: 1.2.8
|
||||
|
||||
'@mapbox/unitbezier@0.0.1': {}
|
||||
|
||||
'@mapbox/vector-tile@1.3.1':
|
||||
|
@ -3031,6 +3058,8 @@ snapshots:
|
|||
- encoding
|
||||
- supports-color
|
||||
|
||||
'@xmldom/xmldom@0.8.10': {}
|
||||
|
||||
abbrev@1.1.1: {}
|
||||
|
||||
acorn-import-attributes@1.9.5(acorn@8.12.0):
|
||||
|
@ -3125,6 +3154,8 @@ snapshots:
|
|||
|
||||
buffer-crc32@1.0.0: {}
|
||||
|
||||
buffer-from@1.1.2: {}
|
||||
|
||||
builtin-modules@3.3.0: {}
|
||||
|
||||
bytewise-core@1.2.3:
|
||||
|
@ -3196,6 +3227,13 @@ snapshots:
|
|||
|
||||
concat-map@0.0.1: {}
|
||||
|
||||
concat-stream@2.0.0:
|
||||
dependencies:
|
||||
buffer-from: 1.1.2
|
||||
inherits: 2.0.4
|
||||
readable-stream: 3.6.2
|
||||
typedarray: 0.0.6
|
||||
|
||||
confbox@0.1.7: {}
|
||||
|
||||
console-control-strings@1.1.0: {}
|
||||
|
@ -4320,6 +4358,8 @@ snapshots:
|
|||
|
||||
type@2.7.3: {}
|
||||
|
||||
typedarray@0.0.6: {}
|
||||
|
||||
typescript@5.5.2: {}
|
||||
|
||||
typewise-core@1.2.0: {}
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
const isImage = ['.jpg', '.jpeg', '.png', '.gif', '.webp'].some((ext) =>
|
||||
attachment.file.endsWith(ext)
|
||||
);
|
||||
return isImage ? `url(${attachment.file})` : 'url(/path/to/default-placeholder.png)';
|
||||
return isImage ? `url(${attachment.file})` : '';
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -4,14 +4,65 @@
|
|||
import type { PageData } from './$types';
|
||||
import { goto } from '$app/navigation';
|
||||
import Lost from '$lib/assets/undraw_lost.svg';
|
||||
import { DefaultMarker, MapLibre, Popup } from 'svelte-maplibre';
|
||||
import { DefaultMarker, MapLibre, Popup, GeoJSON, LineLayer } from 'svelte-maplibre';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { marked } from 'marked'; // Import the markdown parser
|
||||
|
||||
// @ts-ignore
|
||||
import toGeoJSON from '@mapbox/togeojson';
|
||||
|
||||
let geojson: any;
|
||||
|
||||
const renderMarkdown = (markdown: string) => {
|
||||
return marked(markdown);
|
||||
};
|
||||
|
||||
async function getGpxFiles() {
|
||||
let gpxfiles: string[] = [];
|
||||
|
||||
// Collect all GPX file attachments
|
||||
if (adventure.attachments && adventure.attachments.length > 0) {
|
||||
adventure.attachments
|
||||
.filter((attachment) => attachment.extension === 'gpx')
|
||||
.forEach((attachment) => gpxfiles.push(attachment.file));
|
||||
}
|
||||
|
||||
// Initialize a collection GeoJSON object
|
||||
geojson = {
|
||||
type: 'FeatureCollection',
|
||||
features: []
|
||||
};
|
||||
|
||||
// Process each GPX file
|
||||
if (gpxfiles.length > 0) {
|
||||
for (const gpxfile of gpxfiles) {
|
||||
try {
|
||||
let gpxFileName = gpxfile.split('/').pop();
|
||||
let res = await fetch('/gpx/' + gpxFileName);
|
||||
|
||||
if (!res.ok) {
|
||||
console.error(`Failed to fetch GPX file: ${gpxFileName}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
let gpxData = await res.text();
|
||||
let parser = new DOMParser();
|
||||
let gpx = parser.parseFromString(gpxData, 'text/xml');
|
||||
|
||||
// Convert GPX to GeoJSON and merge features
|
||||
let convertedGeoJSON = toGeoJSON.gpx(gpx);
|
||||
if (convertedGeoJSON.features) {
|
||||
geojson.features.push(...convertedGeoJSON.features);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error processing GPX file ${gpxfile}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// Log the final GeoJSON for debugging
|
||||
}
|
||||
}
|
||||
|
||||
export let data: PageData;
|
||||
console.log(data);
|
||||
|
||||
|
@ -32,7 +83,7 @@
|
|||
import ImageDisplayModal from '$lib/components/ImageDisplayModal.svelte';
|
||||
import AttachmentCard from '$lib/components/AttachmentCard.svelte';
|
||||
|
||||
onMount(() => {
|
||||
onMount(async () => {
|
||||
if (data.props.adventure) {
|
||||
adventure = data.props.adventure;
|
||||
// sort so that any image in adventure_images .is_primary is first
|
||||
|
@ -48,6 +99,7 @@
|
|||
} else {
|
||||
notFound = true;
|
||||
}
|
||||
getGpxFiles();
|
||||
});
|
||||
|
||||
function saveEdit(event: CustomEvent<Adventure>) {
|
||||
|
@ -345,6 +397,19 @@
|
|||
center={{ lng: adventure.longitude, lat: adventure.latitude }}
|
||||
zoom={12}
|
||||
>
|
||||
<!-- use the geojson to make a line -->
|
||||
{#if geojson}
|
||||
<!-- Add the GeoJSON data -->
|
||||
<GeoJSON data={geojson}>
|
||||
<LineLayer
|
||||
paint={{
|
||||
'line-color': '#FF0000', // Red line color
|
||||
'line-width': 4 // Adjust the line thickness
|
||||
}}
|
||||
/>
|
||||
</GeoJSON>
|
||||
{/if}
|
||||
|
||||
<!-- MapEvents gives you access to map events even from other components inside the map,
|
||||
where you might not have access to the top-level `MapLibre` component. In this case
|
||||
it would also work to just use on:click on the MapLibre component itself. -->
|
||||
|
|
22
frontend/src/routes/gpx/[file]/+server.ts
Normal file
22
frontend/src/routes/gpx/[file]/+server.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
const PUBLIC_SERVER_URL = process.env['PUBLIC_SERVER_URL'];
|
||||
const endpoint = PUBLIC_SERVER_URL || 'http://localhost:8000';
|
||||
|
||||
/** @type {import('./$types').RequestHandler} */
|
||||
export async function GET(event) {
|
||||
let sessionid = event.cookies.get('sessionid');
|
||||
let fileName = event.params.file;
|
||||
let res = await fetch(`${endpoint}/media/attachments/${fileName}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Cookie: `sessionid=${sessionid}`
|
||||
}
|
||||
});
|
||||
let data = await res.text();
|
||||
return new Response(data, {
|
||||
status: res.status,
|
||||
headers: {
|
||||
'Content-Type': 'application/xml'
|
||||
}
|
||||
});
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue