more file formats && list add in edit
This commit is contained in:
@@ -19,7 +19,7 @@
|
||||
}
|
||||
|
||||
.btn-disabled {
|
||||
@apply bg-gray-500 hover:bg-gray-500
|
||||
@apply bg-menu-item-background-hover border-none text-gray-500 hover:bg-menu-item-background-hover
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"entry": "Eintrag",
|
||||
"error-creating-user": "Fehler beim Erstellen des Nutzers",
|
||||
"error-during-login": "Fehler beim Login",
|
||||
"error-reading-file": "",
|
||||
"est-duration": "Gesch. Dauer",
|
||||
"explore": "Erkunden",
|
||||
"explore-some-trails": "Erkunden Sie einige Routen",
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"entry": "Entry",
|
||||
"error-creating-user": "Error creating user",
|
||||
"error-during-login": "Error during login",
|
||||
"error-reading-file": "Error reading file",
|
||||
"est-duration": "Est. duration",
|
||||
"explore": "Explore",
|
||||
"explore-some-trails": "Explore some trails",
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"entry": "Item",
|
||||
"error-creating-user": "Fout bij het aanmaken van de gebruiker",
|
||||
"error-during-login": "Fout bij het inloggen",
|
||||
"error-reading-file": "",
|
||||
"est-duration": "Geschatte duur",
|
||||
"explore": "Verkennen",
|
||||
"explore-some-trails": "Verken enkele routes",
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"entry": "Pozycja",
|
||||
"error-creating-user": "Błąd tworzenia użytkownika",
|
||||
"error-during-login": "Błąd podczas logowania",
|
||||
"error-reading-file": "",
|
||||
"est-duration": "Szacowany czas",
|
||||
"explore": "Eksploruj",
|
||||
"explore-some-trails": "Eksploruj różne ścieżki",
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"entry": "Entrada",
|
||||
"error-creating-user": "Erro ao criar usuário",
|
||||
"error-during-login": "Erro durante o login",
|
||||
"error-reading-file": "",
|
||||
"est-duration": "Duração prevista",
|
||||
"explore": "Explorar",
|
||||
"explore-some-trails": "Explore algumas trilhas",
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ClientResponseError } from "pocketbase";
|
||||
|
||||
export const categories: Writable<Category[]> = writable([])
|
||||
|
||||
export async function categories_index(f: Function = fetch) {
|
||||
export async function categories_index(f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
|
||||
const r = await f('/api/v1/category', {
|
||||
method: 'GET',
|
||||
})
|
||||
|
||||
@@ -167,7 +167,7 @@ export async function trails_search_bounding_box(northEast: LatLng, southWest: L
|
||||
|
||||
export async function trails_show(id: string, loadGPX?: boolean, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
|
||||
const r = await f(`/api/v1/trail/${id}?` + new URLSearchParams({
|
||||
expand: "category,waypoints,summit_logs",
|
||||
expand: "category,waypoints,summit_logs,lists_via_trails",
|
||||
}), {
|
||||
method: 'GET',
|
||||
})
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Trail } from "$lib/models/trail";
|
||||
import { Waypoint } from "$lib/models/waypoint";
|
||||
import { JSDOM } from "jsdom"
|
||||
import { kml, tcx } from "$lib/vendor/toGeoJSON/toGeoJSON"
|
||||
import GeoJsonToGpx from "$lib/vendor/geoJSONToGPX"
|
||||
|
||||
export function gpx2trail(gpx: string) {
|
||||
export async function gpx2trail(gpx: string) {
|
||||
const JSDOM = (await import("jsdom")).JSDOM
|
||||
const xml = new JSDOM(gpx).window.document
|
||||
const trail = new Trail("");
|
||||
|
||||
@@ -36,3 +38,38 @@ export function gpx2trail(gpx: string) {
|
||||
|
||||
return trail
|
||||
}
|
||||
|
||||
export function fromKML(kmlData: string) {
|
||||
const geojson = kml(new DOMParser().parseFromString(kmlData, "text/xml"))
|
||||
const options = {
|
||||
metadata: {
|
||||
name: '',
|
||||
author: {
|
||||
name: '',
|
||||
link: {
|
||||
href: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const gpx = GeoJsonToGpx(geojson as any, options)
|
||||
|
||||
return new XMLSerializer().serializeToString(gpx)
|
||||
}
|
||||
|
||||
export function fromTCX(tcxData: string) {
|
||||
const geojson = tcx(new DOMParser().parseFromString(tcxData, "text/xml"))
|
||||
const options = {
|
||||
metadata: {
|
||||
name: '',
|
||||
author: {
|
||||
name: '',
|
||||
link: {
|
||||
href: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const gpx = GeoJsonToGpx(geojson as any, options)
|
||||
return new XMLSerializer().serializeToString(gpx)
|
||||
}
|
||||
39
web/src/lib/vendor/geoJSONToGPX/index.d.ts
vendored
Normal file
39
web/src/lib/vendor/geoJSONToGPX/index.d.ts
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
import { Feature, FeatureCollection } from 'geojson';
|
||||
declare interface Bounds {
|
||||
minlat: string;
|
||||
minlon: string;
|
||||
maxlat: string;
|
||||
maxlon: string;
|
||||
}
|
||||
declare interface Copyright {
|
||||
author?: string;
|
||||
year?: string;
|
||||
license?: string;
|
||||
}
|
||||
declare interface Link {
|
||||
href: string;
|
||||
text?: string;
|
||||
type?: string;
|
||||
}
|
||||
declare interface Person {
|
||||
name?: string;
|
||||
email?: string;
|
||||
link?: Link;
|
||||
}
|
||||
declare interface MetaData {
|
||||
name?: string;
|
||||
desc?: string;
|
||||
author?: Person;
|
||||
copyright?: Copyright;
|
||||
link?: Link;
|
||||
time?: string;
|
||||
keywords?: string;
|
||||
bounds?: Bounds;
|
||||
}
|
||||
export declare interface Options {
|
||||
creator?: string;
|
||||
version?: string;
|
||||
metadata?: MetaData;
|
||||
}
|
||||
export default function GeoJsonToGpx(geoJson: Feature | FeatureCollection, options?: Options, implementation?: DOMImplementation): XMLDocument;
|
||||
export {};
|
||||
155
web/src/lib/vendor/geoJSONToGPX/index.js
vendored
Normal file
155
web/src/lib/vendor/geoJSONToGPX/index.js
vendored
Normal file
@@ -0,0 +1,155 @@
|
||||
export default function GeoJsonToGpx(geoJson, options, implementation) {
|
||||
if (implementation === void 0) { implementation = document.implementation; }
|
||||
var doc = implementation.createDocument('http://www.topografix.com/GPX/1/1', '');
|
||||
var instruct = doc.createProcessingInstruction('xml', 'version="1.0" encoding="UTF-8"');
|
||||
doc.appendChild(instruct);
|
||||
var defaultPackageName = '@dwayneparton/geojson-to-gpx';
|
||||
var creator = (options === null || options === void 0 ? void 0 : options.creator) || defaultPackageName;
|
||||
var createElementWithNS = function (tagName) {
|
||||
return doc.createElementNS('http://www.topografix.com/GPX/1/1', tagName);
|
||||
};
|
||||
var gpx = createElementWithNS('gpx');
|
||||
gpx.setAttribute('version', '1.1');
|
||||
gpx.setAttribute('creator', creator);
|
||||
var wpts = [];
|
||||
var trks = [];
|
||||
function createTagInParentElement(parent, tagName, content) {
|
||||
if (content === undefined) {
|
||||
return;
|
||||
}
|
||||
var element = createElementWithNS(tagName);
|
||||
var contentEl = doc.createTextNode(String(content));
|
||||
element.appendChild(contentEl);
|
||||
parent.appendChild(element);
|
||||
}
|
||||
function addSupportedPropertiesFromObject(el, supports, properties) {
|
||||
if (properties && typeof properties === 'object') {
|
||||
supports.forEach(function (key) {
|
||||
var value = properties[key];
|
||||
if (value && typeof value === 'string' && supports.includes(key)) {
|
||||
createTagInParentElement(el, key, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
function createLinkInParentElement(parent, props) {
|
||||
var href = props.href;
|
||||
if (!href) {
|
||||
return;
|
||||
}
|
||||
var el = createElementWithNS('link');
|
||||
el.setAttribute('href', href);
|
||||
addSupportedPropertiesFromObject(el, ['text', 'type'], props);
|
||||
parent.appendChild(el);
|
||||
}
|
||||
function createTrk(properties) {
|
||||
var el = createElementWithNS('trk');
|
||||
var supports = ['name', 'desc', 'src', 'type'];
|
||||
addSupportedPropertiesFromObject(el, supports, properties);
|
||||
return el;
|
||||
}
|
||||
function createPt(type, position, properties) {
|
||||
var lon = position[0], lat = position[1], ele = position[2], time = position[3];
|
||||
var el = createElementWithNS(type);
|
||||
el.setAttribute('lat', String(lat));
|
||||
el.setAttribute('lon', String(lon));
|
||||
createTagInParentElement(el, 'ele', ele);
|
||||
createTagInParentElement(el, 'time', time);
|
||||
var supports = ['name', 'desc', 'src', 'type'];
|
||||
addSupportedPropertiesFromObject(el, supports, properties);
|
||||
return el;
|
||||
}
|
||||
function createTrkSeg(coordinates) {
|
||||
var el = createElementWithNS('trkseg');
|
||||
coordinates.forEach(function (point) {
|
||||
el.appendChild(createPt('trkpt', point));
|
||||
});
|
||||
return el;
|
||||
}
|
||||
function interpretFeature(feature) {
|
||||
var geometry = feature.geometry, properties = feature.properties;
|
||||
var type = geometry.type;
|
||||
switch (type) {
|
||||
case 'Polygon':
|
||||
break;
|
||||
case 'Point': {
|
||||
wpts.push(createPt('wpt', geometry.coordinates, properties));
|
||||
break;
|
||||
}
|
||||
case 'MultiPoint': {
|
||||
geometry.coordinates.forEach(function (coord) {
|
||||
wpts.push(createPt('wpt', coord, properties));
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'LineString': {
|
||||
var lineTrk = createTrk(properties);
|
||||
var trkseg = createTrkSeg(geometry.coordinates);
|
||||
lineTrk.appendChild(trkseg);
|
||||
trks.push(lineTrk);
|
||||
break;
|
||||
}
|
||||
case 'MultiLineString': {
|
||||
var trk_1 = createTrk(properties);
|
||||
geometry.coordinates.forEach(function (pos) {
|
||||
var trkseg = createTrkSeg(pos);
|
||||
trk_1.appendChild(trkseg);
|
||||
});
|
||||
trks.push(trk_1);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (options && typeof options.metadata === 'object') {
|
||||
var meta = options.metadata;
|
||||
var metadata = createElementWithNS('metadata');
|
||||
createTagInParentElement(metadata, 'name', meta.name);
|
||||
createTagInParentElement(metadata, 'desc', meta.desc);
|
||||
if (meta.author && typeof meta.author === 'object') {
|
||||
var author = createElementWithNS('author');
|
||||
createTagInParentElement(author, 'name', meta.author.name);
|
||||
createTagInParentElement(author, 'email', meta.author.email);
|
||||
if (meta.author.link && typeof meta.author.link === 'object') {
|
||||
createLinkInParentElement(author, meta.author.link);
|
||||
}
|
||||
metadata.appendChild(author);
|
||||
}
|
||||
if (typeof meta.copyright === 'object') {
|
||||
var copyright = createElementWithNS('copyright');
|
||||
if (meta.copyright.author) {
|
||||
copyright.setAttribute('author', meta.copyright.author);
|
||||
}
|
||||
createTagInParentElement(copyright, 'year', meta.copyright.year);
|
||||
createTagInParentElement(copyright, 'license', meta.copyright.license);
|
||||
metadata.appendChild(copyright);
|
||||
}
|
||||
if (typeof meta.link === 'object') {
|
||||
createLinkInParentElement(metadata, meta.link);
|
||||
}
|
||||
createTagInParentElement(metadata, 'time', meta.time);
|
||||
createTagInParentElement(metadata, 'keywords', meta.keywords);
|
||||
gpx.appendChild(metadata);
|
||||
}
|
||||
var type = geoJson.type;
|
||||
switch (type) {
|
||||
case 'Feature': {
|
||||
interpretFeature(geoJson);
|
||||
break;
|
||||
}
|
||||
case 'FeatureCollection': {
|
||||
var features = geoJson.features;
|
||||
features.forEach(function (feature) {
|
||||
interpretFeature(feature);
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
wpts.forEach(function (wpt) { return gpx.appendChild(wpt); });
|
||||
trks.forEach(function (trk) { return gpx.appendChild(trk); });
|
||||
doc.appendChild(gpx);
|
||||
return doc;
|
||||
}
|
||||
1157
web/src/lib/vendor/toGeoJSON/toGeoJSON.js
vendored
Normal file
1157
web/src/lib/vendor/toGeoJSON/toGeoJSON.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@@ -92,8 +92,6 @@
|
||||
},
|
||||
})
|
||||
.on("addpoint", function (e: any) {
|
||||
console.log(e);
|
||||
|
||||
if (e.point_type === "start") {
|
||||
const marker: Marker = e.point as Marker;
|
||||
marker.bindPopup(
|
||||
|
||||
@@ -4,15 +4,23 @@
|
||||
import TextField from "$lib/components/base/text_field.svelte";
|
||||
import Textarea from "$lib/components/base/textarea.svelte";
|
||||
import Toggle from "$lib/components/base/toggle.svelte";
|
||||
import ListSelectModal from "$lib/components/list/list_select_modal.svelte";
|
||||
import SummitLogCard from "$lib/components/summit_log/summit_log_card.svelte";
|
||||
import SummitLogModal from "$lib/components/summit_log/summit_log_modal.svelte";
|
||||
import PhotoPicker from "$lib/components/trail/photo_picker.svelte";
|
||||
import WaypointCard from "$lib/components/waypoint/waypoint_card.svelte";
|
||||
import WaypointModal from "$lib/components/waypoint/waypoint_modal.svelte";
|
||||
import type { List } from "$lib/models/list";
|
||||
import { SummitLog } from "$lib/models/summit_log";
|
||||
import { Trail } from "$lib/models/trail";
|
||||
import { Waypoint } from "$lib/models/waypoint";
|
||||
import { categories } from "$lib/stores/category_store";
|
||||
import {
|
||||
lists,
|
||||
lists_add_trail,
|
||||
lists_index,
|
||||
lists_remove_trail,
|
||||
} from "$lib/stores/list_store";
|
||||
import { summitLog } from "$lib/stores/summit_log_store";
|
||||
import { show_toast } from "$lib/stores/toast_store";
|
||||
import {
|
||||
@@ -21,17 +29,19 @@
|
||||
trails_update,
|
||||
} from "$lib/stores/trail_store";
|
||||
import { waypoint } from "$lib/stores/waypoint_store";
|
||||
import { getFileURL } from "$lib/util/file_util";
|
||||
import {
|
||||
formatDistance,
|
||||
formatElevation,
|
||||
formatTimeHHMM,
|
||||
} from "$lib/util/format_util";
|
||||
import { fromKML, fromTCX } from "$lib/util/gpx_util";
|
||||
import { createMarkerFromWaypoint } from "$lib/util/leaflet_util";
|
||||
import "$lib/vendor/leaflet-elevation/src/index.css";
|
||||
import { createForm } from "$lib/vendor/svelte-form-lib";
|
||||
import cryptoRandomString from "crypto-random-string";
|
||||
import { format } from "date-fns";
|
||||
import type { GPX, Icon, LatLng, LeafletEvent, Map } from "leaflet";
|
||||
import type { GPX, Icon, LeafletEvent, Map } from "leaflet";
|
||||
import "leaflet.awesome-markers/dist/leaflet.awesome-markers.css";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
import { onMount } from "svelte";
|
||||
@@ -47,6 +57,7 @@
|
||||
|
||||
let openWaypointModal: () => void;
|
||||
let openSummitLogModal: () => void;
|
||||
let openListSelectModal: () => void;
|
||||
|
||||
let loading = false;
|
||||
|
||||
@@ -281,7 +292,25 @@
|
||||
reader.readAsText(selectedFile);
|
||||
|
||||
reader.onload = async function (e) {
|
||||
await addGPXLayer(e.target?.result as string);
|
||||
let gpxData = "";
|
||||
const fileContent = e.target?.result as string;
|
||||
if (fileContent.includes("http://www.opengis.net/kml")) {
|
||||
gpxData = fromKML(e.target?.result as string);
|
||||
} else if (fileContent.includes("TrainingCenterDatabase")) {
|
||||
gpxData = fromTCX(e.target?.result as string);
|
||||
} else {
|
||||
gpxData = fileContent;
|
||||
}
|
||||
try {
|
||||
await addGPXLayer(gpxData);
|
||||
} catch (e) {
|
||||
show_toast({
|
||||
icon: "close",
|
||||
type: "error",
|
||||
text: $_("error-reading-file"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
const r = await fetch("/api/v1/search/cities500", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
@@ -393,6 +422,28 @@
|
||||
$form.expand.summit_logs = $form.expand.summit_logs;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleListSelection(list: List) {
|
||||
if (!$form.id) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (list.trails?.includes($form.id!)) {
|
||||
await lists_remove_trail(list, $form);
|
||||
} else {
|
||||
await lists_add_trail(list, $form);
|
||||
}
|
||||
await lists_index();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
show_toast({
|
||||
type: "error",
|
||||
icon: "close",
|
||||
text: "Error adding trail to list.",
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -415,7 +466,7 @@
|
||||
type="file"
|
||||
name="gpx"
|
||||
id="fileInput"
|
||||
accept=".gpx"
|
||||
accept=".gpx,.kml,.tcx"
|
||||
style="display: none;"
|
||||
on:change={handleFileSelection}
|
||||
/>
|
||||
@@ -570,6 +621,43 @@
|
||||
on:click={beforeSummitLogModalOpen}
|
||||
><i class="fa fa-plus mr-2"></i>{$_("add-entry")}</button
|
||||
>
|
||||
{#if $lists.length}
|
||||
<hr class="border-separator" />
|
||||
<h3 class="text-xl font-semibold">
|
||||
{$_("list", { values: { n: 2 } })}
|
||||
</h3>
|
||||
<div class="flex gap-4 flex-wrap">
|
||||
{#each $lists as list}
|
||||
{#if $form.id && list.trails?.includes($form.id)}
|
||||
<div
|
||||
class="flex gap-2 items-center border border-input-border rounded-xl p-2"
|
||||
>
|
||||
{#if list.avatar}
|
||||
<img
|
||||
class="w-8 aspect-square rounded-full object-cover"
|
||||
src={getFileURL(list, list.avatar)}
|
||||
alt="avatar"
|
||||
/>
|
||||
{:else}
|
||||
<div
|
||||
class="flex w-4 aspect-square shrink-0 items-center justify-center"
|
||||
>
|
||||
<i class="fa fa-table-list text-5xl"></i>
|
||||
</div>
|
||||
{/if}
|
||||
<span class="text-sm">{list.name}</span>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
<Button
|
||||
secondary={true}
|
||||
disabled={!$form.id}
|
||||
type="button"
|
||||
on:click={openListSelectModal}
|
||||
><i class="fa fa-plus mr-2"></i>{$_("add-to-list")}</Button
|
||||
>
|
||||
{/if}
|
||||
<hr class="border-separator" />
|
||||
<Button
|
||||
primary={true}
|
||||
@@ -587,6 +675,10 @@
|
||||
></WaypointModal>
|
||||
<SummitLogModal bind:openModal={openSummitLogModal} on:save={saveSummitLog}
|
||||
></SummitLogModal>
|
||||
<ListSelectModal
|
||||
bind:openModal={openListSelectModal}
|
||||
on:change={(e) => handleListSelection(e.detail)}
|
||||
></ListSelectModal>
|
||||
|
||||
<style>
|
||||
#map {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Trail } from "$lib/models/trail";
|
||||
import { categories_index } from "$lib/stores/category_store";
|
||||
import { lists_index } from "$lib/stores/list_store";
|
||||
import { trails_show } from "$lib/stores/trail_store";
|
||||
import { error, type Load } from "@sveltejs/kit";
|
||||
|
||||
@@ -8,6 +9,7 @@ export const load: Load = async ({ params, fetch }) => {
|
||||
return error(400, "Bad Request")
|
||||
}
|
||||
const categories = await categories_index(fetch)
|
||||
const lists = await lists_index(undefined, fetch)
|
||||
|
||||
let trail: Trail;
|
||||
if (params.id === "new") {
|
||||
|
||||
Reference in New Issue
Block a user