24 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
vcoppe
0bf168e67e Merge branch 'dev' 2026-04-09 21:10:56 +02:00
vcoppe
d762a45eb9 Merge branch 'dev' 2026-04-09 20:57:30 +02:00
vcoppe
548ab9a459 Merge branch 'dev' 2026-04-07 22:19:15 +02:00
vcoppe
315c1f6a61 Merge branch 'dev' 2026-04-07 22:02:14 +02:00
vcoppe
98257bee12 Merge branch 'dev' 2026-04-06 18:26:22 +02:00
vcoppe
5561b7d9fe Merge branch 'dev' 2026-04-06 14:26:56 +02:00
vcoppe
320887206c Merge branch 'dev' 2026-04-04 23:15:01 +02:00
vcoppe
04a1bf6a55 Merge branch 'dev' 2026-04-03 08:47:57 +02:00
vcoppe
0989371874 Merge branch 'dev' 2026-04-03 08:06:19 +02:00
vcoppe
3dcd6e52d3 Merge branch 'dev' 2026-04-02 22:35:46 +02:00
vcoppe
0f06d0d461 update 2026-04-02 22:23:34 +02:00
vcoppe
db33310a10 revert some changes 2026-04-02 22:20:47 +02:00
9 changed files with 115 additions and 78 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

@@ -18,7 +18,7 @@ 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.
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",

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