1
0
Fork 0
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:
Sean Morley 2025-01-20 20:03:00 -05:00
parent 30c58ca118
commit 64d2bdebce
6 changed files with 131 additions and 5 deletions

View file

@ -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

View file

@ -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",

View file

@ -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: {}

View file

@ -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>

View file

@ -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. -->

View 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'
}
});
}