From eed520a15086f0fee66bab9fd4e1104d2d8a1e79 Mon Sep 17 00:00:00 2001 From: Rub21 Date: Thu, 30 Apr 2026 12:30:55 -0500 Subject: [PATCH 1/4] Update map-styles version to 0.9.15 --- images/web/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/images/web/Dockerfile b/images/web/Dockerfile index fff9d307..1dd20f65 100644 --- a/images/web/Dockerfile +++ b/images/web/Dockerfile @@ -24,7 +24,7 @@ RUN apt-get update && \ && apt-get clean && rm -rf /var/lib/apt/lists/* # Download OHM Website using gitsha, faster than cloning -ENV OPENHISTORICALMAP_WEBSITE_GITSHA=eaaa20fd33d53e1c6d282e3db8e17ac30f27e5d2 +ENV OPENHISTORICALMAP_WEBSITE_GITSHA=4c90d178cdea87b1d8d69ee4b8d99153a8d5f083 ENV OHM_WEBSITE_URL=https://github.com/OpenHistoricalMap/ohm-website/archive/${OPENHISTORICALMAP_WEBSITE_GITSHA}.zip RUN rm -rf $workdir/* && curl -fsSL $OHM_WEBSITE_URL -o /tmp/ohm-website.zip && \ unzip /tmp/ohm-website.zip -d /tmp && \ From d46a5ef57edd1d17c0f63de2ddb0b58cd1c4bab0 Mon Sep 17 00:00:00 2001 From: Rub21 Date: Thu, 30 Apr 2026 16:28:02 -0500 Subject: [PATCH 2/4] Expire tiles deom 0-5 every 24 hours --- images/tiler-varnish/default.vcl | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/images/tiler-varnish/default.vcl b/images/tiler-varnish/default.vcl index 6a242834..3844f899 100644 --- a/images/tiler-varnish/default.vcl +++ b/images/tiler-varnish/default.vcl @@ -51,15 +51,21 @@ sub vcl_backend_response { unset beresp.http.Set-Cookie; if (bereq.url ~ "^/maps/(ne|osm_land)/") { - # Static tiles: storage separado, TTL casi infinito + # Static tiles: separate storage, near-infinite TTL set beresp.storage = storage.static; set beresp.ttl = 365d; set beresp.grace = 30d; set beresp.keep = 7d; + } else if (bereq.url ~ "^/maps/[^/]+/[0-5]/") { + # Dynamic low zooms (0-5): not invalidated via BAN, refreshed by short TTL + set beresp.storage = storage.dynamic; + set beresp.ttl = 24h; + set beresp.grace = 1h; + set beresp.keep = 1d; } else { - # Dynamic tiles: TTL largo como safety net; BAN invalida antes + # Dynamic mid/high zooms (6-20): long TTL; BAN invalidates earlier set beresp.storage = storage.dynamic; - set beresp.ttl = 7d; + set beresp.ttl = 5d; set beresp.grace = 1h; set beresp.keep = 1d; } From 756000d233204c05088012a2369b87b747d6499d Mon Sep 17 00:00:00 2001 From: "Ruben L. Mendoza" Date: Tue, 5 May 2026 15:23:18 -0500 Subject: [PATCH 3/4] Add attribution in vtiles TileJOSN (#754) * Update TileJSON copyright * Display attribution in the tiler viewer * Fix container_name for martin and varnish * Fix attribution in the viewer and staging config --- compose/martin.yml | 4 +- hetzner/deploy.sh | 4 +- hetzner/tiler/tiler.production.yml | 4 +- hetzner/tiler/tiler.staging.yml | 59 ++++++++++++++++--- .../scripts/generate_functions.py | 23 +++++++- images/tiler-server-martin/static/index.html | 32 ++++++---- images/tiler-varnish/default.vcl | 2 +- 7 files changed, 99 insertions(+), 29 deletions(-) diff --git a/compose/martin.yml b/compose/martin.yml index 2bd65a05..200d79d4 100644 --- a/compose/martin.yml +++ b/compose/martin.yml @@ -1,7 +1,7 @@ services: varnish: image: varnish:7.5 - container_name: varnish + container_name: tiler_varnish ports: - "6081:6081" volumes: @@ -18,7 +18,7 @@ services: - martin martin: - container_name: martin + container_name: tiler_server_martin image: rub21/tiler-server-martin:v1 build: context: ../images/tiler-server-martin diff --git a/hetzner/deploy.sh b/hetzner/deploy.sh index e21d9f8a..9726d523 100755 --- a/hetzner/deploy.sh +++ b/hetzner/deploy.sh @@ -42,9 +42,9 @@ if [[ "$ACTION" == "start" || "$ACTION" == "restart" ]] && [ "$AUTO_YES" != "tru fi case "$ACTION" in - start) $COMPOSE up -d ;; + start) $COMPOSE up -d --build;; stop) $COMPOSE down ;; - restart) $COMPOSE up -d --force-recreate ;; + restart) $COMPOSE up -d --force-recreate --build ;; *) echo "Unknown action: $ACTION"; exit 1 ;; esac diff --git a/hetzner/tiler/tiler.production.yml b/hetzner/tiler/tiler.production.yml index 696fbe99..97340790 100644 --- a/hetzner/tiler/tiler.production.yml +++ b/hetzner/tiler/tiler.production.yml @@ -88,9 +88,7 @@ services: ports: - "3030:80" networks: - ohm_network: - aliases: - - martin + - ohm_network tiler_varnish: container_name: tiler_varnish diff --git a/hetzner/tiler/tiler.staging.yml b/hetzner/tiler/tiler.staging.yml index db040d75..64b59340 100644 --- a/hetzner/tiler/tiler.staging.yml +++ b/hetzner/tiler/tiler.staging.yml @@ -1,10 +1,55 @@ services: -volumes: - tiler_pgdata: - driver: local - name: tiler_db_11_02 + tiler_server_martin: + container_name: tiler_server_martin + image: ohm/tiler-server-martin:v1 + build: + context: ../../images/tiler-server-martin + dockerfile: Dockerfile + restart: always + environment: + - OHM_DOMAIN=${OHM_DOMAIN:-openhistoricalmap.org} + env_file: + - .env.tiler + ports: + - "3030:80" + networks: + - ohm_network - tiler_imposm_data: - driver: local - name: tiler_imposm_11_02 + tiler_varnish: + container_name: tiler_varnish + image: varnish:7.5 + restart: always + ports: + - "6081:6081" + volumes: + - ../../images/tiler-varnish/default.vcl:/etc/varnish/default.vcl:ro + tmpfs: + - /var/lib/varnish/varnishd:exec + command: > + varnishd -F + -a :6081 + -f /etc/varnish/default.vcl + -s dynamic=malloc,${VARNISH_DYNAMIC_SIZE:-16G} + -s static=malloc,${VARNISH_STATIC_SIZE:-4G} + -p thread_pool_min=100 + -p thread_pool_max=2000 + -p thread_pools=2 + -p workspace_backend=256k + -p workspace_client=256k + -p http_resp_hdr_len=32k + -p http_resp_size=256k + -p nuke_limit=1000 + -p timeout_idle=30 + -p feature=+http2 + mem_limit: 4G + cpus: "2.0" + ulimits: + memlock: -1 + nofile: + soft: 131072 + hard: 131072 + networks: + - ohm_network + depends_on: + - tiler_server_martin diff --git a/images/tiler-server-martin/scripts/generate_functions.py b/images/tiler-server-martin/scripts/generate_functions.py index d41701b9..ec47048d 100644 --- a/images/tiler-server-martin/scripts/generate_functions.py +++ b/images/tiler-server-martin/scripts/generate_functions.py @@ -41,6 +41,22 @@ def load_config(): } +# Per-group attribution strings (see openhistoricalmap/issues/1343). +# OHM groups link to the copyright page without "©" or "contributors", since +# OHM does not claim copyright over all contributed data. +OHM_ATTRIBUTION = 'OpenHistoricalMap' +OSM_ATTRIBUTION = '© OpenStreetMap contributors' +NE_ATTRIBUTION = 'Natural Earth' + +GROUP_ATTRIBUTION = { + "ohm": OHM_ATTRIBUTION, + "ohm_admin": OHM_ATTRIBUTION, + "ohm_other_boundaries": OHM_ATTRIBUTION, + "osm_land": OSM_ATTRIBUTION, + "ne": NE_ATTRIBUTION, +} + + def get_columns(cur, table_name, exclude): """Get column names from a table/mview, excluding specified columns.""" exclude = list(exclude) + ALWAYS_EXCLUDE @@ -171,14 +187,14 @@ def get_maxzoom(zoom_mapping): return 20 if last_max is None else last_max -def build_tilejson(name, description, tiles_url, vector_layers, minzoom=0, maxzoom=20): +def build_tilejson(name, description, tiles_url, vector_layers, attribution, minzoom=0, maxzoom=20): """Build a TileJSON 3.0.0 manifest.""" return { "tilejson": "3.0.0", "name": name, "description": description, "version": "1.0.0", - "attribution": "© OpenHistoricalMap contributors", + "attribution": attribution, "scheme": "xyz", "tiles": [tiles_url], "minzoom": minzoom, @@ -207,6 +223,7 @@ def generate_tilejson_files(groups, fields_per_function, base_url): for group in groups: group_name = group["name"] + attribution = GROUP_ATTRIBUTION.get(group_name, OHM_ATTRIBUTION) group_vector_layers = [] group_minzoom = 20 group_maxzoom = 0 @@ -231,6 +248,7 @@ def generate_tilejson_files(groups, fields_per_function, base_url): description=f"Layer: {func_def['source_layer']}", tiles_url=f"{base_url}/maps/{group_name}/{fn}/{{z}}/{{x}}/{{y}}", vector_layers=[vl], + attribution=attribution, minzoom=fn_minzoom, maxzoom=fn_maxzoom, ) @@ -246,6 +264,7 @@ def generate_tilejson_files(groups, fields_per_function, base_url): description=f"Composite: {group_name} ({len(group_vector_layers)} layers)", tiles_url=f"{base_url}/maps/{group_name}/{{z}}/{{x}}/{{y}}", vector_layers=group_vector_layers, + attribution=attribution, minzoom=group_minzoom, maxzoom=group_maxzoom, ) diff --git a/images/tiler-server-martin/static/index.html b/images/tiler-server-martin/static/index.html index c7cf3085..aebde2c5 100644 --- a/images/tiler-server-martin/static/index.html +++ b/images/tiler-server-martin/static/index.html @@ -352,7 +352,13 @@

Martin

}; sourceLayerNames[fnName] = vl.id; }); - groupsData.push({ name: groupName, functions: fns }); + groupsData.push({ + name: groupName, + functions: fns, + attribution: tj.attribution, + minzoom: tj.minzoom, + maxzoom: tj.maxzoom, + }); }); showGroupList(); @@ -464,23 +470,20 @@

Martin

} async function addCompositeSource(group) { - const compositeFns = group.functions.join(','); const sourceId = `src-composite-${group.name}`; // Use /maps/{group}/ route which goes through nginx cache + fresh_tiles support, // instead of Martin's native TileJSON URLs which bypass cache. const cachedTileUrl = `${MARTIN_BASE}/maps/${group.name}/{z}/{x}/{y}.pbf`; - const tileJsonUrl = `${MARTIN_BASE}/${compositeFns}`; try { - const resp = await fetch(tileJsonUrl); - const tj = await resp.json(); - - map.addSource(sourceId, { + const compositeSourceOpts = { type: 'vector', tiles: [tileUrlForMode(cachedTileUrl)], - minzoom: tj.minzoom || 0, - maxzoom: tj.maxzoom || 22, - }); + minzoom: group.minzoom != null ? group.minzoom : 0, + maxzoom: group.maxzoom != null ? group.maxzoom : 22, + }; + if (group.attribution) compositeSourceOpts.attribution = group.attribution; + map.addSource(sourceId, compositeSourceOpts); const layerIds = []; const layerByFn = {}; @@ -519,7 +522,7 @@

Martin

}); activeSources['__composite__'] = { sourceId, layerIds, layerByFn, color: '#4fc3f7' }; - if (freshTilesMode || cacheBuster > 0) applyTileUrls(); + if (freshTilesMode) applyTileUrls(); } catch (e) { console.error('Failed to add composite source:', e); } @@ -612,6 +615,11 @@

Martin

minzoom: tj.minzoom || 0, maxzoom: tj.maxzoom || 22, }; + // Prefer group attribution from /maps/{group}.json — Martin's per-function + // TileJSON does not include the custom per-group attribution. + const group = activeGroupIdx != null ? groupsData[activeGroupIdx] : null; + const attribution = (group && group.attribution) || tj.attribution; + if (attribution) sourceOpts.attribution = attribution; if (tj.scheme) sourceOpts.scheme = tj.scheme; if (tj.bounds) sourceOpts.bounds = tj.bounds; map.addSource(sourceId, sourceOpts); @@ -665,7 +673,7 @@

Martin

} activeSources[name] = { sourceId, layerIds, color }; - if (freshTilesMode || cacheBuster > 0) applyTileUrls(); + if (freshTilesMode) applyTileUrls(); const el = document.getElementById(`src-item-${name}`); if (el) el.classList.add('active'); } diff --git a/images/tiler-varnish/default.vcl b/images/tiler-varnish/default.vcl index 3844f899..9cf31d40 100644 --- a/images/tiler-varnish/default.vcl +++ b/images/tiler-varnish/default.vcl @@ -1,7 +1,7 @@ vcl 4.1; backend martin { - .host = "martin"; + .host = "tiler_server_martin"; .port = "80"; .connect_timeout = 10s; .first_byte_timeout = 120s; From 993632c5d12acdc2536f6414a2d63751fd42aa97 Mon Sep 17 00:00:00 2001 From: "Ruben L. Mendoza" Date: Tue, 5 May 2026 15:38:26 -0500 Subject: [PATCH 4/4] Update martin tiler server docker image (#756) * Update TileJSON copyright * Display attribution in the tiler viewer * Fix container_name for martin and varnish * Fix attribution in the viewer and staging config * Update martin vtiles docker image --- hetzner/tiler/tiler.production.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hetzner/tiler/tiler.production.yml b/hetzner/tiler/tiler.production.yml index 97340790..173e500e 100644 --- a/hetzner/tiler/tiler.production.yml +++ b/hetzner/tiler/tiler.production.yml @@ -79,7 +79,7 @@ services: tiler_server_martin: container_name: tiler_server_martin - image: ghcr.io/openhistoricalmap/tiler-server-martin:0.0.1-0.dev.git.3349.h2739b4b1 + image: ghcr.io/openhistoricalmap/tiler-server-martin:0.0.1-0.dev.git.3359.h756000d2 restart: always environment: - OHM_DOMAIN=${OHM_DOMAIN:-openhistoricalmap.org}