Updated README file
This commit is contained in:
@@ -1,40 +1,40 @@
|
|||||||
# Arma raster tiles
|
# Tilemap Server
|
||||||
|
|
||||||
Servidor estático de tiles raster XYZ para os mapas de Arma 3 fornecidos como KML/KMZ `GroundOverlay`. Todas as fontes compõem uma camada mundial única: não existe identificador de mapa na URL.
|
A static XYZ raster-tile server for maps supplied as KML/KMZ `GroundOverlay` files. All sources are composited into one global layer; the URL contains no map identifier.
|
||||||
|
|
||||||
## Arquitetura
|
## Architecture
|
||||||
|
|
||||||
`tile-builder` lê `source/` recursivamente, extrai KMZs somente em diretórios temporários, lê cada `GroundOverlay`, localiza a imagem local indicada por `Icon/href` e a reprojeta para Web Mercator (EPSG:3857). Para `LatLonBox` com `rotation`, os quatro cantos são girados no sentido anti-horário em torno do centro antes do warp GDAL; o raster final recebe alfa para conservar transparência e recortar as bordas rotacionadas.
|
`tile-builder` recursively reads `source/`, extracts KMZ files only into temporary directories, reads each `GroundOverlay`, finds the local image referenced by `Icon/href`, and reprojects it to Web Mercator (EPSG:3857). For a `LatLonBox` with `rotation`, its four corners are rotated counter-clockwise around the center before the GDAL warp. The final raster retains alpha to preserve transparency and clip rotated edges.
|
||||||
|
|
||||||
Cada overlay gera tiles temporários por `gdal2tiles --xyz`. O builder os compõe com alfa em uma única pirâmide. Uma área menor tem prioridade e é desenhada por cima; se as áreas forem iguais, vence primeiro o caminho relativo em ordem alfabética e depois o índice do `GroundOverlay`. A área é calculada a partir do polígono rotacionado em EPSG:3857.
|
Each overlay produces temporary tiles with `gdal2tiles --xyz`. The builder composites them with alpha into a single pyramid. A smaller area takes precedence and is rendered on top; where areas are equal, the relative path in alphabetical order wins, followed by the `GroundOverlay` index. Area is calculated from the rotated polygon in EPSG:3857.
|
||||||
|
|
||||||
O resultado físico é publicado diretamente na pasta local `tiles/`:
|
The generated files are published directly under the local `tiles/` directory:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
tiles/{z}/{x}/{y}.png
|
tiles/{z}/{x}/{y}.png
|
||||||
tiles/metadata.json
|
tiles/metadata.json
|
||||||
```
|
```
|
||||||
|
|
||||||
O Nginx aceita exclusivamente a rota pública abaixo e faz a tradução interna. Não publique nem use a ordem de armazenamento diretamente.
|
Nginx accepts only the public route below and translates it internally. Do not publish or use the storage order directly.
|
||||||
|
|
||||||
```text
|
```text
|
||||||
https://arma_tiles.valmo.dev/tiles/{x}/{y}/{z}
|
https://tiles.example.com/tiles/{x}/{y}/{z}
|
||||||
https://arma_tiles.valmo.dev/tiles/18342/12417/15
|
https://tiles.example.com/tiles/18342/12417/15
|
||||||
```
|
```
|
||||||
|
|
||||||
Mesmo sem `.png` na URL, a resposta é `Content-Type: image/png` e inclui `Access-Control-Allow-Origin: *`, permitindo uso direto pelo MapLibre em outra origem. Sem provider configurado, coordenadas sem cobertura retornam `tiles/empty.png`; com provider configurado, são encaminhadas ao callback e armazenadas no cache local do Nginx.
|
Even without `.png` in the URL, the response uses `Content-Type: image/png` and includes `Access-Control-Allow-Origin: *`, so it can be used directly by MapLibre from another origin. Without a configured provider, uncovered coordinates return `tiles/empty.png`; with a provider, they are forwarded to the callback and cached locally by Nginx.
|
||||||
|
|
||||||
## Provider de callback e MapLibre
|
## Callback provider and MapLibre
|
||||||
|
|
||||||
Copie `.env.example` para `.env` e, se quiser uma camada base, defina um template HTTPS XYZ:
|
Copy `.env.example` to `.env` and, for a base layer, define an HTTPS XYZ template:
|
||||||
|
|
||||||
```dotenv
|
```dotenv
|
||||||
CALLBACK_PROVIDER=https://tiles.seu-provider.example/{z}/{x}/{y}.png
|
CALLBACK_PROVIDER=https://tiles.example-provider.com/{z}/{x}/{y}.png
|
||||||
```
|
```
|
||||||
|
|
||||||
O template é validado antes de qualquer leitura/processamento de fonte. Ele precisa usar `https`, não pode ter query, credenciais ou IP privado, e deve conter exatamente um `{z}`, `{x}` e `{y}` no caminho. Deixe a variável vazia ou ausente para usar o `empty.png` branco.
|
The template is validated before source processing begins. It must use `https`, cannot include a query, credentials, or a private IP address, and must contain exactly one `{z}`, `{x}`, and `{y}` in its path. Leave the variable empty or unset to use the white `empty.png`.
|
||||||
|
|
||||||
Para que o provider apareça tanto onde não há mapa Arma quanto nas bordas transparentes de uma tile parcialmente coberta, use a camada base local abaixo da camada Arma:
|
To show the provider both outside map coverage and through the transparent edges of partially covered tiles, place the local base layer below the overlay layer:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
sources: {
|
sources: {
|
||||||
@@ -44,7 +44,7 @@ sources: {
|
|||||||
tileSize: 256,
|
tileSize: 256,
|
||||||
scheme: "xyz"
|
scheme: "xyz"
|
||||||
},
|
},
|
||||||
arma: {
|
overlays: {
|
||||||
type: "raster",
|
type: "raster",
|
||||||
tiles: ["http://localhost:9000/tiles/{x}/{y}/{z}"],
|
tiles: ["http://localhost:9000/tiles/{x}/{y}/{z}"],
|
||||||
tileSize: 256,
|
tileSize: 256,
|
||||||
@@ -55,74 +55,74 @@ sources: {
|
|||||||
},
|
},
|
||||||
layers: [
|
layers: [
|
||||||
{ id: "provider-base", type: "raster", source: "providerBase" },
|
{ id: "provider-base", type: "raster", source: "providerBase" },
|
||||||
{ id: "arma", type: "raster", source: "arma" }
|
{ id: "overlays", type: "raster", source: "overlays" }
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
`/base/{x}/{y}/{z}` e o fallback de `/tiles/` consultam o callback sob demanda e compartilham um cache local persistente. Exiba a atribuição e cumpra os termos definidos pelo provider escolhido.
|
`/base/{x}/{y}/{z}` and the `/tiles/` fallback request the callback on demand and share a persistent local cache. Display attribution and comply with the selected provider's terms.
|
||||||
|
|
||||||
## Entradas
|
## Inputs
|
||||||
|
|
||||||
Coloque `.kmz` e `.kml` em `source/`, inclusive em subpastas. Um KMZ deve conter um KML (o builder prefere `doc.kml`) e a imagem local referenciada pelo KML. KMLs soltos podem referenciar imagens dentro da mesma árvore de `source/`.
|
Place `.kmz` and `.kml` files in `source/`, including subdirectories. A KMZ must contain one KML file (the builder prefers `doc.kml`) and the local image referenced by the KML. Standalone KML files may reference images within the same `source/` tree.
|
||||||
|
|
||||||
Os arquivos de entrada nunca são modificados ou removidos. Erros por arquivo são registrados em `tiles/metadata.json` e não bloqueiam as demais fontes. Links HTTP(S), caminhos absolutos e `gx:LatLonQuad` não fazem parte desta primeira versão; use `LatLonBox` e imagens locais.
|
Input files are never modified or removed. Per-file errors are recorded in `tiles/metadata.json` and do not prevent other sources from being processed. HTTP(S) links, absolute paths, and `gx:LatLonQuad` are not supported in this first version; use `LatLonBox` and local images.
|
||||||
|
|
||||||
## Zoom e configuração
|
## Zoom and configuration
|
||||||
|
|
||||||
O máximo automático de cada overlay é calculado a partir da maior resolução reprojetada em metros por pixel. O builder escolhe o maior zoom cuja resolução de tile ainda não é mais detalhada que a fonte, evitando ampliação de pixels.
|
The automatic maximum zoom for each overlay is calculated from the highest reprojected resolution in metres per pixel. The builder selects the highest tile zoom whose resolution is no more detailed than the source, avoiding pixel upscaling.
|
||||||
|
|
||||||
O mínimo automático é `0`: os níveis amplos custam pouco e tornam mapas isolados encontráveis a partir da visão mundial. Cada fonte para no próprio máximo nativo, evitando ampliar pixels no servidor. Para permitir zoom visual adicional, configure o MapLibre com `maxZoom` alto e mantenha o `maxzoom` da source no máximo nativo; o MapLibre amplia a última tile disponível. Se nenhuma fonte cobrir a coordenada, o servidor usa o callback configurado ou o `empty.png` branco.
|
The automatic minimum zoom is `0`: broad levels are inexpensive and make isolated maps discoverable from a world view. Each source stops at its native maximum, avoiding server-side pixel upscaling. To allow extra visual zoom, configure MapLibre with a high `maxZoom` and retain the source's native `maxzoom`; MapLibre will enlarge the last available tile. If no source covers the coordinate, the server uses the configured callback or white `empty.png`.
|
||||||
|
|
||||||
`config/maps.json` permite overrides globais e por caminho relativo a `source/`:
|
`config/maps.json` supports global and source-relative overrides:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"global": { "minzoom": null, "maxzoom": null },
|
"global": { "minzoom": null, "maxzoom": null },
|
||||||
"sources": {
|
"sources": {
|
||||||
"Altis.kmz": { "minzoom": 8, "maxzoom": 16 },
|
"example.kmz": { "minzoom": 8, "maxzoom": 16 },
|
||||||
"subpasta/exemplo.kml": { "maxzoom": 14 }
|
"subfolder/example.kml": { "maxzoom": 14 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Use inteiro entre 0 e 22 ou `null`. A precedência é fonte, global, cálculo automático. Um `maxzoom` explícito acima do máximo automático é permitido e ficará marcado em `metadata.json`, pois solicita ampliação conscientemente.
|
Use an integer between 0 and 22 or `null`. Precedence is source, global, then automatic calculation. An explicit `maxzoom` above the automatic maximum is allowed and is marked in `metadata.json`, since it deliberately requests upscaling.
|
||||||
|
|
||||||
## Geração local
|
## Local build
|
||||||
|
|
||||||
Dependências locais: Python 3, Pillow, GDAL com `gdal_translate`, `gdalwarp`, `gdalinfo` e `gdal2tiles.py`.
|
Local dependencies: Python 3, Pillow, and GDAL with `gdal_translate`, `gdalwarp`, `gdalinfo`, and `gdal2tiles.py`.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./scripts/build-tiles.sh
|
./scripts/build-tiles.sh
|
||||||
./scripts/build-tiles.sh --source-filter Altis.kmz
|
./scripts/build-tiles.sh --source-filter example.kmz
|
||||||
```
|
```
|
||||||
|
|
||||||
O cache em `cache/` usa SHA-256 da fonte. Entradas inalteradas reutilizam os GeoTIFFs reprojetados. Para garantir a composição e a prioridade corretas, a v1 recompõe a pirâmide final inteira em staging a cada build; apenas a reprojeção das fontes inalteradas é reutilizada. Ao fim do build, a pasta local `tiles/` é substituída pelo resultado completo, após a geração de `metadata.json`.
|
The `cache/` directory uses the source SHA-256. Unchanged inputs reuse their reprojected GeoTIFFs. To ensure correct compositing and priority, version 1 rebuilds the entire final pyramid in staging for every build; only reprojection of unchanged sources is reused. At the end of a build, the local `tiles/` directory is replaced with the complete result after `metadata.json` is generated.
|
||||||
|
|
||||||
## Docker Compose e Coolify
|
## Docker Compose and Coolify
|
||||||
|
|
||||||
Para desenvolvimento local:
|
For local development:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose up --build
|
docker compose up --build
|
||||||
```
|
```
|
||||||
|
|
||||||
O Nginx ficará disponível em `http://localhost:${HTTP_PORT:-9000}`. O Compose executa primeiro o builder e monta a pasta `tiles/` como somente leitura no Nginx.
|
Nginx is available at `http://localhost:${HTTP_PORT:-9000}`. Compose runs the builder first and mounts `tiles/` read-only in Nginx.
|
||||||
|
|
||||||
No Coolify, mantenha as entradas fora do Git e crie uma pasta/volume persistente no host. Defina `ARMA_TILES_SOURCE_DIR` para esse caminho; ele será montado como `/app/source` somente leitura. O volume nomeado `arma_tiles_data` preserva o cache entre deploys; a pasta `tiles/` do projeto é o volume de saída publicado tanto pelo builder quanto pelo Nginx. Configure o domínio `arma_tiles.valmo.dev` e TLS no proxy do Coolify, apontando para a porta 80 do serviço `tiles`.
|
In Coolify, keep inputs outside Git and create a persistent host directory or volume. Set the source-directory environment variable to that path; it is mounted read-only at `/app/source`. The named data volume preserves the cache between deployments, and the project's `tiles/` directory is the output volume published by both the builder and Nginx. Configure your domain and TLS in the Coolify proxy, targeting port 80 of the `tiles` service.
|
||||||
|
|
||||||
Para limitar paralelismo do GDAL, configure `GDAL2TILES_PROCESSES` (o padrão é `1`). As respostas de tile têm cache público de um dia e `stale-while-revalidate` de sete dias, além de ETag.
|
To limit GDAL parallelism, set `GDAL2TILES_PROCESSES` (default: `1`). Tile responses have one day of public caching plus seven days of `stale-while-revalidate`, as well as an ETag.
|
||||||
|
|
||||||
## Validação
|
## Validation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 tests/test_build_tiles.py
|
python3 tests/test_build_tiles.py
|
||||||
./scripts/smoke-test.sh
|
./scripts/smoke-test.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
O teste de fumaça processa `Altis.kmz`, verifica `tiles/metadata.json` e um PNG, sobe o Nginx e consulta uma tile pela ordem pública `{x}/{y}/{z}` para confirmar `200`, `image/png` e CORS. O callback é opcional e não é exercitado pelo teste local.
|
The smoke test processes the bundled sample KMZ, checks `tiles/metadata.json` and a PNG, starts Nginx, and requests a tile using the public `{x}/{y}/{z}` order to confirm `200`, `image/png`, and CORS. The callback is optional and is not exercised by the local test.
|
||||||
|
|
||||||
## Limitações conhecidas
|
## Known limitations
|
||||||
|
|
||||||
- Não há watcher: após adicionar ou alterar uma entrada, execute o builder ou faça um novo deploy.
|
- There is no watcher: after adding or changing an input, run the builder or deploy again.
|
||||||
- A composição final ainda é uma reconstrução global; a estrutura de cache permite uma futura invalidação apenas dos tiles afetados.
|
- Final compositing is still a global rebuild; the cache structure allows future invalidation of only affected tiles.
|
||||||
- Esta versão não baixa imagens externas, não suporta `gx:LatLonQuad`, não trata overlays que cruzam o antimeridiano e não inclui DEM, hillshade, vetores, MBTiles, frontend, autenticação ou banco de dados.
|
- This version does not download external images, support `gx:LatLonQuad`, handle overlays crossing the antimeridian, or include DEM, hillshade, vectors, MBTiles, a frontend, authentication, or a database.
|
||||||
|
|||||||
Reference in New Issue
Block a user