12 Commits

Author SHA1 Message Date
vcoppe
17559913fd Merge branch 'dev' 2026-04-19 16:49:41 +02:00
vcoppe
dd9aba3adb fine tuning 2026-04-19 16:49:32 +02:00
vcoppe
55590d68a6 Merge branch 'dev' 2026-04-19 16:34:17 +02:00
vcoppe
bd40fbae74 fix routing controls on mobile 2026-04-19 16:34:06 +02:00
vcoppe
690cbc49cc avoid querying features when dragging 2026-04-19 16:31:50 +02:00
vcoppe
8e9f16c460 Merge branch 'dev' 2026-04-19 14:47:35 +02:00
vcoppe
36b16ddeef catch errors when fetching styles and add fallback one 2026-04-19 14:47:19 +02:00
vcoppe
c60b64f24f Merge branch 'dev' 2026-04-17 22:10:40 +02:00
vcoppe
16b8988fa7 fix layer filtering, must allow unknown intermediary keys 2026-04-17 22:10:30 +02:00
vcoppe
fdb6fe4e52 Merge branch 'dev' 2026-04-17 20:11:25 +02:00
Pablo Ovelleiro Corral
40f97b7c35 Fix: overlays bikerouterGravel, cyclOSMlite, mapterhornHillshade, openRailwayMap cannot be toggled in Layer settings (#329) 2026-04-17 20:07:51 +02:00
vcoppe
54b3113480 New Crowdin updates (#326)
* New translations en.json (Spanish)

* New translations merge.mdx (Spanish)

* New translations elevation.mdx (Spanish)

* New translations en.json (Chinese Traditional, Hong Kong)

* New translations en.json (Spanish)

* New translations integration.mdx (Spanish)

* New translations merge.mdx (Spanish)

* New translations integration.mdx (Spanish)

* New translations map-controls.mdx (Spanish)
2026-04-17 20:00:51 +02:00
10 changed files with 120 additions and 83 deletions

View File

@@ -142,6 +142,7 @@ export class MapLayerEventManager {
}
private _handleMouseMove(e: maplibregl.MapMouseEvent) {
if (e.originalEvent.buttons > 0) return;
const featuresByLayer = this._getRenderedFeaturesByLayer(e);
Object.keys(this._listeners).forEach((layerId) => {
const features = featuresByLayer[layerId] || [];

View File

@@ -81,8 +81,13 @@ export class StyleManager {
let basemap = get(currentBasemap);
const basemapInfo = basemaps[basemap] ?? custom[basemap]?.value ?? basemaps[defaultBasemap];
const basemapStyle = await this.get(basemapInfo);
let basemapStyle = basemaps.openStreetMap as maplibregl.StyleSpecification;
try {
basemapStyle = await this.get(basemapInfo);
} catch (e) {
console.error(e.message);
}
this.merge(style, basemapStyle);
if (this._maptilerKey !== '') {
@@ -109,16 +114,21 @@ export class StyleManager {
if (!layers[overlay]) {
if (this._pastOverlays.has(overlay)) {
const overlayInfo = custom[overlay]?.value ?? overlays[overlay];
try {
const overlayStyle = await this.get(overlayInfo);
for (let layer of overlayStyle.layers ?? []) {
if (map_.getLayer(layer.id)) {
map_.removeLayer(layer.id);
}
}
} catch (e) {
// Should not happen
}
this._pastOverlays.delete(overlay);
}
} else {
const overlayInfo = custom[overlay]?.value ?? overlays[overlay];
try {
const overlayStyle = await this.get(overlayInfo);
const opacity = overlayOpacities[overlay];
@@ -146,8 +156,10 @@ export class StyleManager {
map_.addLayer(layer, ANCHOR_LAYER_KEY.overlays);
}
}
this._pastOverlays.add(overlay);
} catch (e) {
console.error(e.message);
}
}
}
} catch (e) {}
@@ -181,6 +193,9 @@ export class StyleManager {
styleUrl = styleUrl.replace(maptilerKeyPlaceHolder, this._maptilerKey);
}
const response = await fetch(styleUrl, { cache: 'force-cache' });
if (!response.ok) {
throw new Error(`HTTP error fetching style "${styleInfo}": ${response.status}`);
}
const style = await response.json();
return style;
} else {

View File

@@ -57,8 +57,10 @@ export class RoutingControls {
updateControlsBinded: () => void = this.updateControls.bind(this);
appendAnchorBinded: (e: MapMouseEvent) => void = this.appendAnchor.bind(this);
addIntermediateAnchorBinded: (e: MapMouseEvent) => void = this.addIntermediateAnchor.bind(this);
draggedAnchorIndex: number | null = null;
lastDraggedAnchorEventTime: number = 0;
draggingStartingPosition: maplibregl.Point = new maplibregl.Point(0, 0);
onMouseEnterBinded: () => void = this.onMouseEnter.bind(this);
onMouseLeaveBinded: () => void = this.onMouseLeave.bind(this);
@@ -133,6 +135,7 @@ export class RoutingControls {
map_.on('style.load', this.updateControlsBinded);
map_.on('click', this.appendAnchorBinded);
layerEventManager.on('mousemove', this.fileId, this.showTemporaryAnchorBinded);
layerEventManager.on('click', this.fileId, this.addIntermediateAnchorBinded);
this.fileUnsubscribe = this.file.subscribe(this.updateControlsBinded);
}
@@ -237,6 +240,7 @@ export class RoutingControls {
map_?.off('style.load', this.updateControlsBinded);
map_?.off('click', this.appendAnchorBinded);
layerEventManager?.off('mousemove', this.fileId, this.showTemporaryAnchorBinded);
layerEventManager?.off('click', this.fileId, this.addIntermediateAnchorBinded);
map_?.off('mousemove', this.updateTemporaryAnchorBinded);
this.layers.forEach((layer) => {
@@ -521,12 +525,19 @@ export class RoutingControls {
if (get(streetViewEnabled) && get(streetViewSource) === 'google') {
return;
}
if (
this.draggedAnchorIndex !== null ||
Date.now() - this.lastDraggedAnchorEventTime < 100
) {
// Exit if anchor is being dragged
return;
}
if (
e.target.queryRenderedFeatures(e.point, {
layers: [...this.layers.values()].map((layer) => layer.id),
layers: [this.fileId, ...[...this.layers.values()].map((layer) => layer.id)],
}).length
) {
// Clicked on routing control, ignoring
// Clicked on routing control or layer, ignoring
return;
}
this.appendAnchorWithCoordinates({
@@ -598,6 +609,15 @@ export class RoutingControls {
await this.routeBetweenAnchors([lastAnchor, newAnchor], [lastAnchorPoint, newAnchorPoint]);
}
addIntermediateAnchor(e: maplibregl.MapMouseEvent) {
e.preventDefault();
if (this.temporaryAnchor !== null) {
this.turnIntoPermanentAnchor();
return;
}
}
getNeighbouringAnchors(anchor: Anchor): [Anchor | null, Anchor | null] {
let previousAnchor: Anchor | null = null;
let nextAnchor: Anchor | null = null;
@@ -818,8 +838,11 @@ export class RoutingControls {
onClick(e: MapLayerMouseEvent) {
e.preventDefault();
if (this.temporaryAnchor !== null) {
this.turnIntoPermanentAnchor();
if (
this.draggedAnchorIndex !== null ||
Date.now() - this.lastDraggedAnchorEventTime < 100
) {
// Exit if anchor is being dragged
return;
}
@@ -908,6 +931,8 @@ export class RoutingControls {
lat: e.lngLat.lat,
lon: e.lngLat.lng,
});
this.lastDraggedAnchorEventTime = Date.now();
}
onMouseUp(e: MapLayerMouseEvent | MapLayerTouchEvent) {
@@ -946,6 +971,7 @@ export class RoutingControls {
}
this.draggedAnchorIndex = null;
this.lastDraggedAnchorEventTime = Date.now();
}
showTemporaryAnchor(e: MapLayerMouseEvent) {

View File

@@ -13,8 +13,8 @@ Puede usar **gpx.studio** para crear mapas que muestren sus archivos GPX e integ
Todo lo que necesita es:
1. GPX files hosted on your server or on Google Drive, or accessible via a public URL;
2. _Optional:_ a <a href="https://cloud.maptiler.com/auth/widget?next=https://cloud.maptiler.com/maps/" target="_blank">MapTiler key</a> to load MapTiler maps.
1. Archivos GPX alojados en su servidor o en Google Drive, o accesibles a través de una URL pública;
2. _Opcional:_ una <a href="https://cloud.maptiler.com/auth/widget?next=https://cloud.maptiler.com/maps/" target="_blank">Tecla MapTiler</a> para cargar mapas MapTiler.
Luego puede jugar con el configurador de abajo para personalizar su mapa y generar el código HTML correspondiente.

View File

@@ -69,4 +69,4 @@ Pueden activarse en la [configuración de capas del mapa](./menu/settings).
En estos ajustes, también puede administrar la opacidad de las capas superpuestas.
For advanced users, it is possible to add custom basemaps and overlays by providing <a href="https://en.wikipedia.org/wiki/Web_Map_Tile_Service" target="_blank">WMTS</a>, <a href="https://en.wikipedia.org/wiki/Web_Map_Service" target="_blank">WMS</a>, or <a href="https://maplibre.org/maplibre-style-spec/" target="_blank">MapLibre style JSON</a> URLs.
Para los usuarios avanzados, es posible añadir mapas base y superposiciones personalizadas proporcionando <a href="https://en.wikipedia.org/wiki/Web_Map_Tile_Service" target="_blank">WMTS</a>, <a href="https://en.wikipedia.org/wiki/Web_Map_Service" target="_blank">WMS</a> o URLs <a href="https://docs.mapbox.com/help/glossary/style/" target="_blank">JSON estilo Mapbox</a>.

View File

@@ -10,7 +10,7 @@ title: Desnivel
# <MountainSnow size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
Le permite añadir datos de desnivel a trazas y [puntos de interés](../gpx), o reemplazar los datos existentes.
Esta herramienta permite añadir datos de elevación a los rastros y [puntos de interés](../gpx), o reemplazar los datos existentes.
<div class="flex flex-row justify-center">
<Elevation class="text-foreground p-3 border rounded-md shadow-lg" />
@@ -18,7 +18,7 @@ Le permite añadir datos de desnivel a trazas y [puntos de interés](../gpx), o
<DocsNote>
Elevation data is provided by <a href="https://maptiler.com" target="_blank">MapTiler</a>.
You can learn more about its origin and accuracy in the <a href="https://docs.maptiler.com/guides/map-tiling-hosting/data-hosting/rgb-terrain-by-maptiler/" target="_blank">documentation</a>.
Los datos de notificación son proporcionados por <a href="https://maptiler.com" target="_blank">MapTiler</a>.
Puedes aprender más sobre su origen y precisión en la <a href="https://docs.maptiler.com/guides/map-tiling-hosting/data-hosting/rgb-terrain-by-maptiler/" target="_blank">documentación</a>.
</DocsNote>

View File

@@ -18,8 +18,8 @@ Para usar esta herramienta, necesita [seleccionar](../files-and-stats) múltiple
<DocsNote>
Selected items are merged in the order they appear in the files list.
Reorder items by drag-and-drop if needed.
Los elementos seleccionados se combinan en el orden en que aparecen en la lista de archivos.
Si es necesario, puede reordenar los elementos arrastrando y soltando.
</DocsNote>

View File

@@ -162,38 +162,33 @@ function getLayerValidator(allowed: Record<string, any>, fallback: string) {
function filterLayerTree(t: LayerTreeType, allowed: LayerTreeType | undefined): LayerTreeType {
const filtered: LayerTreeType = {};
const values = Object.values(t);
if (values.length == 0) return filtered;
if (typeof values[0] === 'boolean') {
if (allowed) {
Object.keys(allowed).forEach((key) => {
Object.entries(allowed).forEach(([key, value]) => {
if (Object.hasOwn(t, key)) {
if (typeof value === 'boolean') {
filtered[key] = t[key];
} else if (typeof value === 'object') {
filtered[key] = filterLayerTree(
typeof t[key] === 'object' ? t[key] : {},
value
);
}
} else {
filtered[key] = allowed[key];
}
});
}
Object.entries(t).forEach(([key, value]) => {
if (
!Object.hasOwn(filtered, key) &&
(key.startsWith('custom-') || key.startsWith('extension-'))
) {
filtered[key] = value;
}
});
} else {
}
Object.entries(t).forEach(([key, value]) => {
if (typeof value === 'object') {
filtered[key] = filterLayerTree(
value,
typeof allowed === 'object' && typeof allowed[key] === 'object'
? allowed[key]
: undefined
);
if (!Object.hasOwn(filtered, key)) {
if (typeof value === 'boolean') {
if (key.startsWith('custom-') || key.startsWith('extension-')) {
filtered[key] = value;
}
} else if (typeof value === 'object') {
filtered[key] = filterLayerTree(value, undefined);
}
}
});
}
return filtered;
}

View File

@@ -2,10 +2,10 @@
"metadata": {
"home_title": "el editor online de archivos GPX",
"app_title": "app",
"embed_title": " editor online de archivos GPX",
"embed_title": "Editor online de archivos GPX",
"help_title": "ayuda",
"404_title": "página no encontrada",
"description": "Mira, edita y crea archivos GPX online con planificación avanzada de rutas y herramientas de procesamiento de archivos, bonitos mapas y visualizaciones detalladas de datos."
"description": "Visualiza, edita y crea archivos GPX online con planificación avanzada de rutas y herramientas de procesamiento de archivos, bonitos mapas y visualizaciones detalladas de datos."
},
"menu": {
"new": "Nuevo",
@@ -234,7 +234,7 @@
},
"elevation": {
"button": "Solicitar datos de desnivel",
"help": "Requesting elevation data will erase the existing elevation data, if any, and replace it with data from MapTiler.",
"help": "La solicitud de datos de desnivel borrará los datos de desnivel existentes, si los hay, y los reemplazará con datos de Mapbox.",
"help_no_selection": "Seleccione un elemento del archivo para solicitar datos de desnivel."
},
"waypoint": {
@@ -276,7 +276,7 @@
"new": "Nueva capa personalizada",
"edit": "Editar capa personalizada",
"urls": "URL(s)",
"url_placeholder": "WMTS, WMS or MapLibre style JSON",
"url_placeholder": "WMTS, WMS o JSON estilo Mapbox",
"max_zoom": "Zoom máximo",
"layer_type": "Tipo de capa",
"basemap": "Mapa base",
@@ -494,7 +494,7 @@
"email": "Email",
"contribute": "Contribuir",
"supported_by": "con el apoyo de",
"features": "Features",
"features": "Características",
"route_planning": "Planificación de ruta",
"route_planning_description": "Una interfaz intuitiva para crear itinerarios adaptados a cada deporte, basada en datos de OpenStreetMap.",
"file_processing": "Procesamiento avanzado de archivo",
@@ -503,15 +503,15 @@
"maps_description": "Una gran colección de mapas base, capas y puntos de interés para ayudarle a crear su próxima aventura al aire libre o visualizar su último logro.",
"data_visualization": "Visualización de datos",
"data_visualization_description": "Un perfil de elevación interactivo con estadísticas detalladas para analizar actividades registradas y futuros objetivos.",
"philosophy": "Philosophy",
"foss": "Free, ad-free and open source",
"foss_description": "The website is free to use, without ads, and the source code is publicly available on GitHub.",
"privacy": "Privacy-friendly",
"philosophy": "Filosofía",
"foss": "Gratis, sin anuncios y código abierto",
"foss_description": "El sitio web es de uso gratuito, sin anuncios, y el código fuente está disponible públicamente en GitHub.",
"privacy": "Respetuosa con la privacidad",
"privacy_description": "Tus archivos GPX nunca abandonan tu navegador. Sin seguimiento, sin recopilación de datos.",
"community": "Made possible by the community",
"community": "Posible gracias a la comunidad",
"community_description": "gpx.studio tiene una comunidad asombrosa que ha cubierto sus costes a través de donaciones durante años, mientras ha dado forma al proyecto a través de sugerencias de características, informes de fallos y traducciones a muchos idiomas.",
"support_button": "Apoya a gpx.studio en Open Collective",
"translate_button": "Help translate the website on Crowdin"
"translate_button": "Ayuda a traducir el sitio web en Crowdin"
},
"docs": {
"translate": "Mejorar la traducción en Crowdin",
@@ -536,7 +536,7 @@
},
"embedding": {
"title": "Crear su propio mapa",
"maptiler_key": "MapTiler key (optional, only required for MapTiler maps)",
"maptiler_key": "Clave MapTiler (opcional, sólo se requiere para mapas MapTiler)",
"file_urls": "URLs de archivo (separados por comas)",
"drive_ids": "IDs de archivo de Google Drive (separados por comas)",
"basemap": "Mapa base",

View File

@@ -16,9 +16,9 @@
"duplicate": "Duplicate",
"copy": "Copy",
"paste": "Paste",
"cut": "Cut",
"export": "Export...",
"export_all": "Export all...",
"cut": "剪下",
"export": "匯出……",
"export_all": "匯出所有……",
"export_options": "Export options",
"support_message": "The tool is free to use, but not free to run. Please consider supporting the website if you use it frequently. Thank you!",
"support_button": "Help keep the website free",