Manual/Referencia

Formato de proyecto de Meratyn

Los proyectos se guardan como archivos .meratyn.json.

El archivo es una sobrescritura recursiva del proyecto predeterminado canónico de Meratyn, no un volcado completo del estado de ejecución. Un proyecto intacto por tanto se exporta como {}. Los objetos contienen solo las claves modificadas; el contenido ordenado del proyecto, como capas, widgets del tablero y capítulos de la historia, permanece como arrays completos cuando están presentes. Al abrir un archivo, sus valores se superponen a los mismos valores predeterminados antes de que el proyecto se cargue en la app.

Esquema de sobrescritura

Cada campo siguiente es opcional en el archivo. Un campo omitido conserva su valor predeterminado canónico.

CampoTipoDescripción
versionstringVersión del formato (0.2.0)
namestringNombre visible del proyecto
mapViewobjectcenter, zoom, bearing, pitch, bbox opcional
mapControlsobjectSobrescrituras opcionales de visibilidad y posición de esquina para los controles de mapa integrados del proyecto
basemapStyleUrlstringURL de estilo JSON de MapLibre, o cadena vacía para un fondo en blanco
basemapVisiblebooleanSi la capa de Fondo es visible
basemapOpacitynumberOpacidad de la capa de Fondo de 0 a 1
layersarrayDefiniciones de capas (ver abajo)
stylesobjectMapa de id de capa → LayerStyle
pluginsobjectURLs de manifiesto de plugins externos opcionales, IDs de plugins activos, posiciones de controles de mapa de plugins y ajustes de plugins
legendobjectPersonalizaciones opcionales de la leyenda del Diseño de impresión (título, agrupación, orden, renombrar/ocultar por elemento)
storymapobjectStory map dirigido por scroll opcional (capítulos y ajustes de presentación); se omite cuando no hay capítulos
widgetsarrayWidgets de gráficos opcionales del panel Tablero (ver abajo); se omite cuando no hay ninguno
dashboardColumnsnumberNúmero opcional de columnas de la cuadrícula de widgets del Tablero (1-6, por defecto 2); se omite si es el predeterminado
styleLibraryarrayEntradas opcionales del Gestor de estilos con alcance de proyecto (nombre, etiquetas, tipo, subconjunto LayerStyle); se omite si está vacía
commentsarrayHilos de revisión opcionales anclados a un punto o feature (ver abajo); se omite cuando no hay ninguno
metadataobjectMetadatos de proyecto de forma libre

Espacio de trabajo de exhibición pública

Los ejemplos públicos administrados pueden optar por un espacio de trabajo enfocado con alcance de proyecto. Estos metadatos abren una superficie de función relevante y permiten en lista blanca solo los menús, comandos, fuentes de datos y plugins que usa el escenario:

{
  "metadata": {
    "publicShowcase": true,
    "showcase": {
      "surface": "processing-history",
      "workspace": {
        "allowedMenus": ["project", "view", "processing", "controls"],
        "allowedMenuItems": [
          "project.share",
          "view.zoomIn",
          "processing.whitebox",
          "processing.history"
        ],
        "allowedDataSources": [],
        "allowedPlugins": ["maplibre-gl-components", "maplibre-gl-swipe"],
        "allowedComponentControls": ["terrain"]
      }
    }
  }
}

El tiempo de ejecución honra showcase.workspace solo cuando publicShowcase es exactamente true. Decodifica las listas blancas en estado desechable de sesión de proyecto y las interseca con el perfil de interfaz local del usuario. El espacio de trabajo puede por tanto ocultar superficies de descubrimiento adicionales, pero no puede revelar un elemento oculto por el usuario ni sobrescribir sus preferencias. Ajustes → Interfaz sigue siendo alcanzable. Los proyectos ordinarios ignoran estos metadatos de presentación.

allowedComponentControls es una lista blanca completa opcional para la cuadrícula de controles de MapLibre Components. Una lista vacía oculta la cuadrícula conservando el tiempo de ejecución de plugins necesario para las capas restauradas de COG, Zarr y otras propiedad de plugins; omitir el campo preserva la cuadrícula completa para los proyectos ordinarios.

showcase.guide es la guía bilingüe del proyecto que se abre desde el mapa. Contiene title, introduction, steps ordenados (title y body), requirements y un glossary opcional de entradas { term, definition }, cada una un par { en, es }. Los términos del glosario que aparecen en la introducción, los pasos o los requisitos se convierten en explicaciones en línea, buscadas sin distinguir mayúsculas y como palabras completas, para que compradores sin formación SIG puedan seguir la guía.

showcase.translations muestra el resto del proyecto en el idioma de la interfaz. Asocia un código de idioma con una tabla cuyas claves son los textos visibles del proyecto en inglés, por ejemplo { "es": { "Fatal crashes": "Siniestros fatales" } }. Al abrir un proyecto, la aplicación reemplaza los textos visibles (nombres de capas, grupos y nodos de modelos; títulos de widgets, leyendas e historias; etiquetas de campos, clases y leyendas; descripciones y métodos de procedencia) por la entrada de la tabla para el idioma actual. Los identificadores, las URL, los nombres de campos que leen los estilos y filtros y los valores de los datos nunca cambian, así que un proyecto traducido se dibuja y consulta exactamente igual que el original. La category de un widget puede apuntar a un campo con las mismas categorías en el idioma de destino, como "pattern_en": "pattern_es". Un texto que ya está en otro idioma vuelve primero al inglés, así que un proyecto guardado mientras estaba traducido se reabre correctamente; un idioma sin tabla usa el inglés. Los casos de Explore guardan sus tablas en scripts/assets/showcases/translations/<slug>.es.json, y bun scripts/assets/explore-catalog.ts --check falla cuando un texto visible no tiene traducción o una entrada ya no se usa.

Estado de plugins

{
  "manifestUrls": ["https://example.com/plugins/example-plugin/plugin.json"],
  "activePluginIds": ["maplibre-layer-control", "maplibre-gl-swipe"],
  "mapControlPositions": {
    "maplibre-layer-control": "top-right",
    "maplibre-gl-swipe": "top-left"
  },
  "settings": {
    "maplibre-gl-swipe": {
      "orientation": "vertical",
      "position": 50,
      "collapsed": false,
      "active": true,
      "leftLayers": ["layer-a"],
      "rightLayers": ["layer-b"]
    }
  }
}

Los proyectos sin sección plugins abren con el estado de plugins predeterminado integrado.

Controles de mapa integrados

mapControls guarda solo los valores que difieren de los predeterminados integrados de Navigation, Fullscreen, Compass, Geolocate, Globe, Terrain, Scale, Attribution, Logo y Maptoolkit Logo. La sección se omite cuando cada control está en su valor predeterminado. Al abrir, estas sobrescrituras dispersas se fusionan sobre los valores canónicos, de modo que el menú de Controles, el JSON guardado y el mapa restaurado leen un único contrato.

{
  "visibility": { "geolocate": true },
  "positions": {
    "geolocate": "bottom-left"
  }
}

Los controles propiedad de plugins permanecen bajo plugins; no se duplican en mapControls.

Credenciales en la salida externa

Los archivos de proyecto pueden contener URLs de servicios y configuración de plugins. Antes de que un proyecto se comparta, se exporte como HTML, se envíe a un host de incrustación externo, se sincronice mediante colaboración o se guarde en la biblioteca de capas guardadas (Mis datos), Meratyn crea una copia desacoplada que elimina los campos de credenciales, los parámetros de URL firmados y el userinfo. Las variables de entorno del tiempo de ejecución y las claves de geocodificadores se limpian, y plugins.settings opaco se limpia por completo. El GeoJSON inline sigue siendo datos y se copia sin cambios.

Un Guardado local ordinario ofrece una elección explícita para conservar credenciales en ese archivo de proyecto local; las rutas de salida externa nunca ofrecen esa anulación.

El widget Python co-localizado existente puede solicitar fidelidad completa del proyecto mediante trustedWidget: true en su mensaje de host aceptado. Este puente estrecho de widget no es una concesión general de confianza: las incrustaciones externas ordinarias permanecen con credenciales redactadas.

Comentarios

comments guarda hilos de revisión acotados. Cada comentario tiene un id, un author (name y color hex), body no vacío, createdAt con marca de tiempo válida, bandera resolved y replies; cada respuesta tiene su propio id, author, body y createdAt. Los anclajes de punto guardan lngLat; los anclajes de feature guardan layerId y featureId de cadena o numérico, con lngLat opcional como posición de mapa de respaldo.

{
  "comments": [
    {
      "id": "comment-1",
      "anchor": { "type": "point", "lngLat": [-75.5636, 6.2442] },
      "author": { "name": "Ada", "color": "#123abc" },
      "body": "Verify this boundary.",
      "createdAt": "2026-08-04T05:00:00.000Z",
      "resolved": false,
      "replies": [
        {
          "id": "reply-1",
          "author": { "name": "Lin", "color": "#0f766e" },
          "body": "Confirmed.",
          "createdAt": "2026-08-04T05:05:00.000Z"
        }
      ]
    },
    {
      "id": "comment-2",
      "anchor": {
        "type": "feature",
        "layerId": "parcels",
        "featureId": "42",
        "lngLat": [-74.071, 4.712]
      },
      "author": { "name": "Ada", "color": "#123abc" },
      "body": "Check the owner attribute.",
      "createdAt": "2026-08-04T05:10:00.000Z",
      "resolved": true,
      "replies": []
    }
  ]
}

Leyenda

La leyenda del Diseño de impresión siempre se deriva de la simbología de las capas visibles; el objeto legend guarda solo las ediciones del usuario superpuestas, de modo que las personalizaciones sobreviven a añadidos y eliminaciones de capas.

{
  "title": "Legend",
  "groupByLayer": true,
  "order": ["layer-b", "layer-a"],
  "overrides": {
    "layer-a": { "label": "Roads" },
    "layer-b::0": { "label": "Low" },
    "layer-b::1": { "hidden": true }
  }
}
  • title — encabezado dibujado sobre las entradas de la leyenda.
  • groupByLayer — cuando es true, las clases graduadas/categorizadas se agrupan bajo un encabezado por capa; cuando es false, las clases se listan planas.
  • order — orden de las entradas de nivel superior por id de capa (primero el superior); las capas no listadas conservan su orden predeterminado después de las listadas.
  • overrides — ediciones de label y hidden por elemento indexadas por una clave de elemento estable: un id de capa para una entrada completa, o ${layerId}::${index} para una clase individual dentro de una entrada graduada/categorizada.

Los proyectos sin sección legend abren con la leyenda predeterminada (autogenerada de las capas, titulada “Legend”).

Story map

Un story map convierte el proyecto en una narrativa dirigida por scroll. Cada capítulo captura una vista de cámara más texto, y puede desvanecer capas del proyecto al entrar/salir. La sección se omite por completo cuando el proyecto no tiene capítulos.

{
  "title": "A Tour of Three Cities",
  "subtitle": "Built with Meratyn",
  "byline": "By the Meratyn team",
  "footer": "Source: OpenStreetMap",
  "theme": "dark",
  "showMarkers": true,
  "markerColor": "#3fb1ce",
  "inset": false,
  "insetPosition": "bottom-right",
  "chapters": [
    {
      "id": "intro",
      "title": "Cartagena",
      "description": "A walled city on the Caribbean coast. <em>HTML allowed.</em>",
      "image": "https://example.com/cartagena.jpg",
      "alignment": "left",
      "hidden": false,
      "location": { "center": [-75.5144, 10.391], "zoom": 11, "pitch": 45, "bearing": 0 },
      "mapAnimation": "flyTo",
      "rotateAnimation": false,
      "onChapterEnter": [{ "layerId": "layer-a", "opacity": 1, "duration": 2000 }],
      "onChapterExit": [{ "layerId": "layer-a", "opacity": 0 }]
    }
  ]
}

alignment es uno de left, center, right, full; mapAnimation es flyTo, easeTo o jumpTo. Los cambios de opacidad de capas referencian ids de capas del proyecto. Construye y presenta story maps desde Proyecto → Story Map, o exporta una página HTML autocontenida para alojamiento estático.

Widgets del tablero

{
  "widgets": [
    { "id": "w1", "layerId": "layer-a", "type": "histogram", "field": "pop", "bins": 12 },
    {
      "id": "w2",
      "layerId": "layer-a",
      "type": "bar",
      "category": "kind",
      "aggregation": "sum",
      "valueField": "pop",
      "title": "Population by kind"
    },
    {
      "id": "w3",
      "layerId": "layer-a",
      "type": "selector",
      "category": "kind",
      "multiple": true,
      "selection": ["residential", "commercial"]
    }
  ]
}

Cada widget vincula un gráfico a los atributos de una capa. type es uno de histogram, scatter, bar, line, box, pie, indicator o selector. Qué otras claves aplican depende del tipo: field (histogram/line/box), xField/yField (scatter), category + aggregation + valueField (bar/pie), bins (histogram). La aggregation de bar es count/sum/mean; la de pie es solo count/sum. title es una etiqueta opcional y color un hex opcional (#rgb/#rrggbb) para las marcas del gráfico (el color de serie para gráficos de una sola serie; la base de una rampa monocromática para bar/pie). Un indicator puede usar indicatorAggregation (count, sum, mean, min, max o median) y lee field para toda agregación salvo count, más prefix y suffix opcionales. Un selector exige category; su bandera opcional multiple permite más de un valor, y su selection persistida se normaliza a cadenas únicas no vacías (o un valor cuando multiple se omite o es false). Las claves no usadas se ignoran. El panel Tablero (Herramientas → Tablero, o el botón Tablero en la tabla de atributos) también guarda dashboardColumns, el número de columnas de la cuadrícula de widgets (1-6, por defecto 2), en el nivel superior del proyecto. Los gráficos leen de capas vectoriales respaldadas por GeoJSON y de capas de consulta DuckDB; los widgets vinculados a una capa ausente o sin atributos se muestran vacíos.

Objeto de capa

{
  "id": "uuid",
  "name": "My Layer",
  "type": "geojson",
  "source": { "type": "geojson" },
  "visible": true,
  "opacity": 1,
  "style": {
    "minZoom": 0,
    "maxZoom": 24,
    "fillColor": "#3b82f6",
    "strokeColor": "#1e40af",
    "strokeWidth": 2,
    "strokeWidthUnit": "pixels",
    "strokeOpacity": 1,
    "fillOpacity": 0.6,
    "circleRadius": 6,
    "rasterBrightnessMin": 0,
    "rasterBrightnessMax": 1,
    "rasterSaturation": 0,
    "rasterContrast": 0,
    "rasterHueRotate": 0
  },
  "metadata": {},
  "geojson": { "type": "FeatureCollection", "features": [] },
  "sourcePath": "/path/to/file.geojson"
}

Los estilos vectoriales también pueden guardar cadenas de expresión tipadas de MapLibre en fillOpacityExpression, strokeColorExpression, strokeOpacityExpression, strokeWidthExpression y circleRadiusExpression. Cada expresión sobrescribe su valor literal correspondiente mientras sea válida. El decodificador del proyecto valida estos campos de forma independiente con Effect Schema, así que una expresión editada a mano inválida se descarta sin descartar el resto del estilo de la capa.

Un marcador de punto personalizado (markerShape: "custom") guarda su origen en markerSvg: marcado SVG en línea, o una URL data: o http(s): de una imagen SVG, PNG, JPEG o GIF. Cualquier otro esquema de URL se ignora. Un estilo graduado cuyos cortes se editaron a mano registra vectorStyleClassificationScheme: "manual"; los cortes guardados se usan tal cual. vectorStyleColorRampReversed: true recorre vectorStyleColorRamp desde su último color hasta el primero cada vez que se regeneran las clases; su valor predeterminado es false. Un estilo categorizado con vectorStyleAllCategories: true clasifica cada valor distinto, así que su número de clases sigue el total de categorías del campo; su valor predeterminado es false. Las etiquetas que dan formato a un campo numérico guardan labels.numberFormatEnabled, labels.numberDecimals (un entero de 0 a 10; cualquier otro valor restaura el predeterminado) y labels.numberLocale (una etiqueta BCP 47, o vacía para seguir el idioma de la aplicación).

Exclusiones de exportación de la tabla de atributos

Para una capa vectorial, metadata.columnSettings.excluded puede contener nombres de propiedades que deben omitirse de la salida de datos externa. Esto es distinto de hidden, que controla solo la vista local de la tabla de atributos y nunca elimina datos. Los guardados locales del proyecto conservan el conjunto completo de propiedades. Meratyn aplica excluded solo a las copias externas desacopladas para exportaciones vectoriales, HTML independiente, subidas a Share, to_project() de Python y otras salidas externas; la capa en memoria y su geometría, IDs de feature, propiedades nulas y metadatos no relacionados permanecen sin cambios.

{
  "metadata": {
    "columnSettings": {
      "hidden": ["internal_note"],
      "excluded": ["personal_email"]
    }
  }
}

Referencias de capas en línea

Las capas añadidas por el plugin Catálogo integrado conservan su fuente MapLibre actual más una referencia estable de Share en metadata:

{
  "metadata": {
    "hostedSourceKind": "mappview-hosted",
    "hostedLayerId": "8ab66014-6c72-42b5-b2f5-c1aa7909c77b",
    "hostedServiceId": "wms",
    "hostedServiceType": "wms",
    "hostedStableUrl": "https://share.example/api/layers/8ab66014-6c72-42b5-b2f5-c1aa7909c77b/gateway/wms",
    "shareBaseUrl": "https://share.example"
  }
}

La app puede usar temporalmente una URL de acceso de corta vida o una autorización en memoria mientras una capa privada está abierta. La serialización del proyecto elimina esas credenciales de tiempo de ejecución y restaura hostedStableUrl y hostedStableSource. Un consumidor por tanto resuelve el acceso con su propia sesión de Share; un archivo de proyecto nunca otorga acceso llevando el token de su autor.

El ID estable de la capa sobrevive al reemplazo de datos, cambios de metadatos, cambios de estilo y migraciones de proveedor. No persistas en un proyecto el ID de recurso de un servidor de implementación ni una URL de servicio interna.

Para capas WFS GetFeature y GeoJSON por URL, metadata.refresh puede persistir un intervalo opcional de auto-refresco. intervalMs puede ser cualquier intervalo positivo en milisegundos:

{
  "metadata": {
    "refresh": { "enabled": true, "intervalMs": 60000 }
  }
}

El refresco manual usa la misma URL de fuente guardada sin necesidad de estos metadatos.

Las capas GeoJSON por URL y de consulta de entidades ArcGIS se cargan después de abrir el proyecto: el mapa y las demás capas aparecen de inmediato, y cada capa de servicio se muestra cargando hasta que llegan sus filas. Una capa puede omitir geojson y guardar solo su URL de fuente. Si incluye filas, se muestran al instante y se reemplazan por las del servicio cuando su refresco está activo; con el refresco desactivado se usan tal cual. Un servicio que falla reporta un problema en su capa y nunca impide abrir el proyecto.

Para capas vectoriales de archivo local en la app de escritorio, metadata.watch puede persistir el conmutador “vigilar este archivo por cambios”. Cuando está habilitado, la app de escritorio registra un vigilante del sistema de archivos que recarga las features de la capa desde sourcePath cada vez que el archivo cambia en disco:

{
  "metadata": {
    "watch": { "enabled": true }
  }
}

La clave se omite cuando la vigilancia está apagada, y no tiene efecto fuera del host de escritorio (el navegador no puede vigilar una ruta de sistema de archivos local).

Una capa puede llevar uniones de atributos persistentes (Propiedades de capa → Uniones): left joins vivos que materializan columnas de la tabla de atributos de otra capa — típicamente una tabla sin geometría añadida vía Texto delimitado sin campos de coordenadas — en las propiedades de las features de esta capa, emparejadas por un campo clave:

{
  "joins": [
    {
      "id": "uuid",
      "joinLayerId": "other-layer-uuid",
      "targetField": "name",
      "joinField": "state_name",
      "fields": ["pop_2025", "median_income"],
      "prefix": "census_",
      "enabled": true,
      "addedFields": ["census_pop_2025", "census_median_income"],
      "stats": {
        "matchedCount": 50,
        "unmatchedTargetCount": 2,
        "unmatchedJoinCount": 2
      }
    }
  ]
}

fields (subconjunto a traer; omitido = todos los campos salvo la clave), prefix y enabled son opcionales. addedFields y stats son contabilidad del motor, reescritos en cada aplicación: addedFields lista las columnas de salida que la unión añadió (para que re-aplicar pueda eliminarlas primero, manteniendo la operación idempotente), y stats registra los últimos conteos de coincidencia mostrados en la interfaz de Uniones. Las uniones se re-resuelven contra el conjunto de capas cargado al abrir el proyecto, así que una copia guardada de la salida unida se auto-repara si la tabla de unión cambió.

Cuando una capa geojson habilita style.simpleStyleEnabled, las features individuales pueden sobrescribir el estilo de la capa con propiedades de simplestyle-spec (stroke, fill, stroke-width, fill-opacity, …). Meratyn también honra un text-color por feature en puntos de marcador de texto (usado por la capa de Anotaciones), recurriendo a style.textColor cuando una feature no lo establece.

Tipos de capa

TipoEstado en v1.0
geojsonSoportado para archivos importados y URLs de GeoJSON
xyzSoportado para plantillas de teselas ráster
wmsSoportado como capas WMS GetMap en teselas
rasterSoportado para plantillas de teselas ráster
vector-tilesSoportado para fuentes de teselas vectoriales MapLibre
mbtilesSoportado en la app de escritorio mediante un protocolo MapLibre local
arcgisSoportado para capas ArcGIS FeatureServer y VectorTileServer; los servicios renderizados MapServer/ImageServer se guardan como capas raster con metadatos ArcGIS
pmtilesSoportado por el plugin Components
cogSoportado para capas ráster COG y GeoTIFF
flatgeobufSoportado por el plugin Components e importado como GeoJSON al cargarse como archivo vectorial local
zarrSoportado por el plugin Components
lidarSoportado por el plugin Components
gaussian-splatSoportado por el plugin Components
geoparquetImportado como GeoJSON vía DuckDB-WASM
duckdb-querySoportado para capas de resultados de consultas SQL
3d-tilesSoportado por el plugin maplibre-gl-3d-tiles

Una capa geojson cargada página a página desde una consulta de ArcGIS FeatureServer o MapServer guarda source.arcgisQueryUrl, source.pageSize y source.maxFeatures, más un filtro SQL opcional source.arcgisWhere. El filtro se aplica a cada página y al conteo de entidades, de modo que una capa como “solo siniestros fatales” sigue filtrada al actualizarla o reabrirla.

API

import { createEmptyProject, parseProject, serializeProject } from "@mappview/core/project";