3 Commits

Author SHA1 Message Date
vcoppe
9ca46b9d35 small fix 2025-12-24 17:21:26 +01:00
vcoppe
7c2e24bbc4 draft support for graphhopper 2025-12-23 16:49:47 +01:00
vcoppe
e92e48ffde New Crowdin updates (#290)
* New translations en.json (Spanish)

* New translations getting-started.mdx (Dutch)

* New translations edit.mdx (Dutch)

* New translations en.json (Basque)

* New translations file.mdx (Basque)

* New translations en.json (Chinese Simplified)

* New translations file.mdx (Chinese Simplified)
2025-12-18 17:41:09 +01:00
10 changed files with 147 additions and 41 deletions

View File

@@ -1375,10 +1375,7 @@ export class TrackPoint {
: undefined;
}
setExtensions(extensions: Record<string, string>) {
if (Object.keys(extensions).length === 0) {
return;
}
setExtension(key: string, value: string) {
if (!this.extensions) {
this.extensions = {};
}
@@ -1388,8 +1385,12 @@ export class TrackPoint {
if (!this.extensions['gpxtpx:TrackPointExtension']['gpxtpx:Extensions']) {
this.extensions['gpxtpx:TrackPointExtension']['gpxtpx:Extensions'] = {};
}
Object.entries(extensions).forEach(([key, value]) => {
this.extensions['gpxtpx:TrackPointExtension']['gpxtpx:Extensions'][key] = value;
}
setExtensions(extensions: Record<string, string>) {
Object.entries(extensions).forEach(([key, value]) => {
this.setExtension(key, value);
});
}

View File

@@ -21,7 +21,7 @@
SquareArrowUpLeft,
SquareArrowOutDownRight,
} from '@lucide/svelte';
import { brouterProfiles } from '$lib/components/toolbar/tools/routing/routing';
import { routingProfiles } from '$lib/components/toolbar/tools/routing/routing';
import { i18n } from '$lib/i18n.svelte';
import { slide } from 'svelte/transition';
import {
@@ -167,7 +167,7 @@
{i18n._(`toolbar.routing.activities.${$routingProfile}`)}
</Select.Trigger>
<Select.Content>
{#each Object.keys(brouterProfiles) as profile}
{#each Object.keys(routingProfiles) as profile}
<Select.Item value={profile}
>{i18n._(
`toolbar.routing.activities.${profile}`

View File

@@ -6,35 +6,141 @@ import { get } from 'svelte/store';
const { routing, routingProfile, privateRoads } = settings;
export const brouterProfiles: { [key: string]: string } = {
bike: 'Trekking-dry',
racing_bike: 'fastbike',
gravel_bike: 'gravel',
mountain_bike: 'MTB',
foot: 'Hiking-Alpine-SAC6',
motorcycle: 'Car-FastEco',
water: 'river',
railway: 'rail',
export type RoutingProfile = {
engine: 'graphhopper' | 'brouter';
profile: string;
};
export const routingProfiles: { [key: string]: RoutingProfile } = {
bike: { engine: 'graphhopper', profile: 'bike' },
racing_bike: { engine: 'graphhopper', profile: 'racingbike' },
gravel_bike: { engine: 'brouter', profile: 'gravel' },
mountain_bike: { engine: 'graphhopper', profile: 'mtb' },
foot: { engine: 'graphhopper', profile: 'foot' },
motorcycle: { engine: 'graphhopper', profile: 'motorcycle' },
water: { engine: 'brouter', profile: 'river' },
railway: { engine: 'brouter', profile: 'rail' },
};
export function route(points: Coordinates[]): Promise<TrackPoint[]> {
if (get(routing)) {
return getRoute(points, brouterProfiles[get(routingProfile)], get(privateRoads));
const profile = routingProfiles[get(routingProfile)];
if (profile.engine === 'graphhopper') {
return getGraphHopperRoute(points, profile.profile, get(privateRoads));
} else {
return getBRouterRoute(points, profile.profile);
}
} else {
return getIntermediatePoints(points);
}
}
async function getRoute(
const graphhopperDetails = ['road_class', 'surface', 'hike_rating', 'mtb_rating'];
const hikeRatingToSACScale: { [key: string]: string } = {
'1': 'hiking',
'2': 'mountain_hiking',
'3': 'demanding_mountain_hiking',
'4': 'alpine_hiking',
'5': 'demanding_alpine_hiking',
'6': 'difficult_alpine_hiking',
};
const mtbRatingToScale: { [key: string]: string } = {
'1': '0',
'2': '1',
'3': '2',
'4': '3',
'5': '4',
'6': '5',
'7': '6',
};
async function getGraphHopperRoute(
points: Coordinates[],
brouterProfile: string,
graphHopperProfile: string,
privateRoads: boolean
): Promise<TrackPoint[]> {
let url = `https://brouter.gpx.studio?lonlats=${points.map((point) => `${point.lon.toFixed(8)},${point.lat.toFixed(8)}`).join('|')}&profile=${brouterProfile + (privateRoads ? '-private' : '')}&format=geojson&alternativeidx=0`;
let response = await fetch('https://graphhopper-a.gpx.studio/route', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
points: points.map((point) => [point.lon, point.lat]),
profile: graphHopperProfile,
elevation: true,
points_encoded: false,
details: graphhopperDetails,
custom_model: privateRoads
? {}
: {
priority: [
{
if: 'road_access == PRIVATE',
multiply_by: '0.0',
},
],
},
}),
});
if (!response.ok) {
throw new Error(`${await response.text()}`);
}
let json = await response.json();
let route: TrackPoint[] = [];
let coordinates = json.paths[0].points.coordinates;
let details = json.paths[0].details;
for (let i = 0; i < coordinates.length; i++) {
route.push(
new TrackPoint({
attributes: {
lat: coordinates[i][1],
lon: coordinates[i][0],
},
ele: coordinates[i][2] ?? (i > 0 ? route[i - 1].ele : 0),
extensions: {},
})
);
}
for (let key of graphhopperDetails) {
let detail = details[key];
for (let i = 0; i < detail.length; i++) {
for (let j = detail[i][0]; j < detail[i][1] + (i == detail.length - 1); j++) {
if (detail[i][2] !== undefined && detail[i][2] !== 'missing') {
if (key === 'road_class') {
route[j].setExtension('highway', detail[i][2]);
} else if (key === 'hike_rating') {
const sacScale = hikeRatingToSACScale[detail[i][2]];
if (sacScale) {
route[j].setExtension('sac_scale', sacScale);
}
} else if (key === 'mtb_rating') {
const mtbScale = mtbRatingToScale[detail[i][2]];
if (mtbScale) {
route[j].setExtension('mtb_scale', mtbScale);
}
} else if (key === 'surface' && detail[i][2] !== 'other') {
route[j].setExtension('surface', detail[i][2]);
}
}
}
}
}
return route;
}
async function getBRouterRoute(
points: Coordinates[],
brouterProfile: string
): Promise<TrackPoint[]> {
let url = `https://brouter.de/brouter?lonlats=${points.map((point) => `${point.lon.toFixed(8)},${point.lat.toFixed(8)}`).join('|')}&profile=${brouterProfile}&format=geojson&alternativeidx=0`;
let response = await fetch(url);
// Check if the response is ok
if (!response.ok) {
throw new Error(`${await response.text()}`);
}
@@ -52,14 +158,13 @@ async function getRoute(
let tags = messageIdx < messages.length ? getTags(messages[messageIdx][tagIdx]) : {};
for (let i = 0; i < coordinates.length; i++) {
let coord = coordinates[i];
route.push(
new TrackPoint({
attributes: {
lat: coord[1],
lon: coord[0],
lat: coordinates[i][1],
lon: coordinates[i][0],
},
ele: coord[2] ?? (i > 0 ? route[i - 1].ele : 0),
ele: coordinates[i][2] ?? (i > 0 ? route[i - 1].ele : 0),
})
);

View File

@@ -29,13 +29,13 @@ Beste era batez, fitxategiak zuzenean arrastatu eta jaregin ditzakezu zure fitxa
Sortu hautatutako fitxategien kopia bat.
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Delete
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Ezabatu
Delete the currently selected files.
Ezabatu hautatutako fitxategiak.
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Delete all
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Ezabatu guztiak
Delete all files.
Ezabatu fitxategi guztiak.
### <Download size="16" class="inline-block" style="margin-bottom: 2px" /> Esportatu...

View File

@@ -14,7 +14,7 @@ Deze handleiding zal je door alle componenten en gereedschappen van de interface
<DocsImage src="getting-started/interface" alt="De gpx.studio interface." />
Zoals weergegeven in bovenstaande scherm, is de interface verdeeld in vier hoofddelen rond de kaart.
Voordat we in de details van elke sectie duiken, hebben we een snel overzicht van de interface.
Voordat we in de details van elke sectie duiken, eerst een snel overzicht van de interface.
## Menu

View File

@@ -83,7 +83,7 @@ Deze actie is alleen beschikbaar wanneer de verticale indeling van de bestandsli
### <ClipboardPaste size="16" class="inline-block" style="margin-bottom: 2px" /> Plakken
Plak de bestandsitems van het klembord naar het huidige hiërarchie niveau indien compatibel.
Plak de bestandsitems van het klembord naar het huidige hiërarchieniveau indien compatibel.
<DocsNote>

View File

@@ -33,9 +33,9 @@ title: 文件
Delete the currently selected files.
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Delete all
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> 删除全部
Delete all files.
删除全部文件。
### <Download size="16" class="inline-block" style="margin-bottom: 2px" /> 导出...

View File

@@ -80,7 +80,7 @@
"center": "Centrar",
"open_in": "Abrir en",
"copy_coordinates": "Copiar coordenadas",
"edit_osm": "Edit in OpenStreetMap"
"edit_osm": "Editar en OpenStreetMap"
},
"toolbar": {
"routing": {
@@ -353,7 +353,7 @@
"water": "Agua",
"shower": "Ducha",
"shelter": "Refugio",
"cemetery": "Cemetery",
"cemetery": "Cementerio",
"motorized": "Coches y motos",
"fuel-station": "Gasolinera",
"parking": "Aparcamiento",

View File

@@ -28,7 +28,7 @@
"undo": "Desegin",
"redo": "Berregin",
"delete": "Ezabatu",
"delete_all": "Delete all",
"delete_all": "Ezabatu guztiak",
"select_all": "Hautatu dena",
"view": "Ikusi",
"elevation_profile": "Altuera profila",
@@ -80,7 +80,7 @@
"center": "Erdiratu",
"open_in": "Ireki hemen",
"copy_coordinates": "Kopiatu koordenatuak",
"edit_osm": "Edit in OpenStreetMap"
"edit_osm": "Editatu OpenStreeMapen"
},
"toolbar": {
"routing": {
@@ -353,7 +353,7 @@
"water": "Ura",
"shower": "Dutxa",
"shelter": "Babeslekua",
"cemetery": "Cemetery",
"cemetery": "Hilerria",
"motorized": "Kotxeak eta motorrak",
"fuel-station": "Gasolindegia",
"parking": "Aparkalekua",

View File

@@ -28,7 +28,7 @@
"undo": "撤销",
"redo": "恢复",
"delete": "删除",
"delete_all": "",
"delete_all": "全部删除",
"select_all": "全选",
"view": "显示",
"elevation_profile": "海拔剖面图",
@@ -80,7 +80,7 @@
"center": "居中",
"open_in": "打开于",
"copy_coordinates": "复制坐标",
"edit_osm": "Edit in OpenStreetMap"
"edit_osm": " OpenStreetMap 中编辑"
},
"toolbar": {
"routing": {
@@ -353,7 +353,7 @@
"water": "饮用水",
"shower": "淋浴",
"shelter": "庇护所",
"cemetery": "Cemetery",
"cemetery": "墓地",
"motorized": "汽车和摩托车",
"fuel-station": "加油站",
"parking": "停车场",