Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
# Optional browser override. The default is the generated shared style.
VITE_OCCUMED_STYLE_URL=/style/occumed-open.json

# Optional build-time endpoints. Defaults use no-key public open-data services.
OCCUMED_TILEJSON_URL=https://tiles.openfreemap.org/planet
OCCUMED_GLYPHS_URL=https://tiles.openfreemap.org/fonts/{fontstack}/{range}.pbf
# The browser always uses one same-origin vector URL. This override is intended
# only for a compatible deployment of the Occu-Med virtual tile gateway.
OCCUMED_VECTOR_TILES_URL=__OCCUMED_PUBLIC_ORIGIN__/tiles/{z}/{x}/{y}.pbf

# Optional build-time terrain endpoint. The vector basemap has no external
# fallback and glyphs are rendered locally by MapLibre.
OCCUMED_TERRAIN_URL=https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png
OCCUMED_RELIEF_URL=https://tiles.openfreemap.org/natural_earth/ne2sr/{z}/{x}/{y}.png

# Set this once on the deployed Map service so the shared style contains
# an absolute sprite URL usable by Atlas, Insight Hub, Network Map, etc.
# Example: https://map.example.com
PUBLIC_ORIGIN=

# Server-only PMTiles storage configuration. These URLs are never placed in the
# browser style.
OCCUMED_WORLD_RELEASE_REPOSITORY=Occumed79/Map
OCCUMED_WORLD_RELEASE_TAG=occumed-world-v1
OCCUMED_WORLD_MANIFEST_URL=
OCCUMED_TILE_CACHE_MAX_BYTES=134217728
27 changes: 18 additions & 9 deletions .github/actions/build-pmtiles-shard-final/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,21 @@ runs:
> "$assets_json"
row="$(
jq -r --arg name "$ASSET_NAME" \
'[.[][] | select(.name == $name)][0] | if . == null then "" else [.id, .name, .size] | @tsv end' \
'[.[][] | select(.name == $name)][0] | if . == null then "" else [.id, .name, .size, .state] | @tsv end' \
"$assets_json"
)"
rm -f "$assets_json"
asset_id="$(printf '%s' "$row" | cut -f1)"
asset_size="$(printf '%s' "$row" | cut -f3)"
if [ -n "$asset_id" ] && [ -n "$asset_size" ] && [ "$asset_size" -gt 0 ]; then
asset_state="$(printf '%s' "$row" | cut -f4)"
if [ -n "$asset_id" ] && [ -n "$asset_size" ] && [ "$asset_size" -gt 0 ] && [ "$asset_state" = uploaded ]; then
echo 'exists=true' >> "$GITHUB_OUTPUT"
echo "Reusing published asset: $ASSET_NAME ($asset_size bytes)"
else
if [ -n "$asset_id" ]; then
echo "Deleting incomplete release asset before rebuild: $ASSET_NAME ($asset_state, $asset_size bytes)"
gh api --method DELETE "/repos/${GITHUB_REPOSITORY}/releases/assets/${asset_id}"
fi
echo 'exists=false' >> "$GITHUB_OUTPUT"
fi

Expand Down Expand Up @@ -113,14 +118,14 @@ runs:
set -euo pipefail
release_id="$(gh api "/repos/${GITHUB_REPOSITORY}/releases/tags/${RELEASE_TAG}" --jq '.id')"

lookup_size() {
lookup_asset() {
local assets_json
assets_json="$(mktemp)"
gh api --paginate --slurp \
"/repos/${GITHUB_REPOSITORY}/releases/${release_id}/assets?per_page=100" \
> "$assets_json"
jq -r --arg name "$ASSET_NAME" \
'[.[][] | select(.name == $name)][0] | if . == null then "" else (.size | tostring) end' \
'[.[][] | select(.name == $name)][0] | if . == null then "" else [(.size | tostring), .state] | @tsv end' \
"$assets_json"
rm -f "$assets_json"
}
Expand All @@ -132,17 +137,21 @@ runs:
uploaded=true
break
fi
size="$(lookup_size)"
if [ -n "$size" ] && [ "$size" -gt 0 ]; then
echo "Asset appeared during upload: $ASSET_NAME ($size bytes)"
row="$(lookup_asset)"
size="$(printf '%s' "$row" | cut -f1)"
state="$(printf '%s' "$row" | cut -f2)"
if [ -n "$size" ] && [ "$size" -gt 0 ] && [ "$state" = uploaded ]; then
echo "Asset appeared during upload: $ASSET_NAME ($size bytes, $state)"
uploaded=true
break
fi
sleep $((attempt * 10))
done

size="$(lookup_size)"
if [ "$uploaded" != true ] || [ -z "$size" ] || [ "$size" -le 0 ]; then
row="$(lookup_asset)"
size="$(printf '%s' "$row" | cut -f1)"
state="$(printf '%s' "$row" | cut -f2)"
if [ "$uploaded" != true ] || [ -z "$size" ] || [ "$size" -le 0 ] || [ "$state" != uploaded ]; then
echo "Release asset is still missing after upload: $ASSET_NAME" >&2
exit 1
fi
Expand Down
22 changes: 19 additions & 3 deletions .github/workflows/audit-world-release-v2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,21 +47,25 @@ jobs:
> final-world-audit/release-pages.json
gh release download "$WORLD_RELEASE_TAG" \
--repo "$GITHUB_REPOSITORY" \
--pattern world-manifest.json \
--pattern world-virtual-manifest.json \
--dir final-world-audit/release

node <<'NODE'
const fs = require('node:fs');

const plan = JSON.parse(fs.readFileSync('final-world-audit/pinned-plan.json', 'utf8'));
const pages = JSON.parse(fs.readFileSync('final-world-audit/release-pages.json', 'utf8'));
const manifest = JSON.parse(fs.readFileSync('final-world-audit/release/world-manifest.json', 'utf8'));
const manifest = JSON.parse(fs.readFileSync('final-world-audit/release/world-virtual-manifest.json', 'utf8'));
const regions = plan.include || [];
const assets = pages.flatMap((page) => page || []);
const byName = new Map(assets.map((asset) => [asset.name, asset]));

const plannedNames = regions.map((region) => region.asset_name);
const plannedNameSet = new Set(plannedNames);
const virtualAssetNames = new Set([
'occumed-world-overview.pmtiles',
'occumed-world-surface.pmtiles'
]);
const plannedIds = regions.map((region) => region.id);
const plannedIdSet = new Set(plannedIds);

Expand All @@ -71,7 +75,12 @@ jobs:

const missing = regions.filter((region) => !byName.has(region.asset_name));
const zeroSize = regions.filter((region) => Number(byName.get(region.asset_name)?.size || 0) <= 0);
const extras = assets.filter((asset) => asset.name.endsWith('.pmtiles') && !plannedNameSet.has(asset.name));
const incomplete = regions.filter((region) => byName.get(region.asset_name)?.state !== 'uploaded');
const extras = assets.filter((asset) =>
asset.name.endsWith('.pmtiles') &&
!plannedNameSet.has(asset.name) &&
!virtualAssetNames.has(asset.name)
);

const manifestRegions = manifest.regions || [];
const manifestNames = manifestRegions.map((region) => region.asset);
Expand Down Expand Up @@ -107,6 +116,10 @@ jobs:
if (manifestNameSet.size !== manifestNames.length) structuralErrors.push('manifest contains duplicate assets');
if (manifestIdSet.size !== manifestIds.length) structuralErrors.push('manifest contains duplicate IDs');
if ((manifest.missingRegions || []).length !== 0) structuralErrors.push('manifest missingRegions is not empty');
if (Number(manifest.version) !== 2) structuralErrors.push(`manifest version=${manifest.version}, expected=2`);
if (manifest.virtualTiles?.endpoint !== '/tiles/{z}/{x}/{y}.pbf') structuralErrors.push('manifest virtual tile endpoint is missing or incorrect');
if (manifest.virtualTiles?.overviewAsset !== 'occumed-world-overview.pmtiles') structuralErrors.push('manifest overview asset is missing or incorrect');
if (manifest.virtualTiles?.surfaceAsset !== 'occumed-world-surface.pmtiles') structuralErrors.push('manifest surface asset is missing or incorrect');

const summary = {
capturedAt: new Date().toISOString(),
Expand All @@ -116,6 +129,7 @@ jobs:
publishedCanonicalCount: regions.length - missing.length,
missingCanonicalCount: missing.length,
zeroSizeCanonicalCount: zeroSize.length,
incompleteCanonicalCount: incomplete.length,
extraPmtilesAssetCount: extras.length,
manifestMissingRegionCount: Number(manifest.missingRegionCount),
manifestPlannedRegionCount: Number(manifest.plannedRegionCount),
Expand All @@ -129,11 +143,13 @@ jobs:
structuralErrors,
missing: missing.map((region) => ({ id: region.id, asset: region.asset_name })),
zeroSize: zeroSize.map((region) => ({ id: region.id, asset: region.asset_name })),
incomplete: incomplete.map((region) => ({ id: region.id, asset: region.asset_name })),
extraPmtilesAssets: extras.map((asset) => ({ name: asset.name, size: Number(asset.size || 0) })),
binaryRangeAndRenderValidation: 'deferred_to_fresno_runtime_browser_validation',
passed:
missing.length === 0 &&
zeroSize.length === 0 &&
incomplete.length === 0 &&
extras.length === 0 &&
absentFromManifest.length === 0 &&
unexpectedInManifest.length === 0 &&
Expand Down
10 changes: 9 additions & 1 deletion .github/workflows/audit-world-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,16 @@ jobs:
const missingIds = new Set(missing.map((region) => region.id));
const repairTargets = plan.include.filter((region) => missingIds.has(region.id) || !healthyIds.has(region.id));
const canonicalAssetNames = new Set(plan.include.map((region) => region.asset_name));
const virtualAssetNames = new Set([
'occumed-world-overview.pmtiles',
'occumed-world-surface.pmtiles'
]);
const extraPmtilesAssets = release.assets
.filter((asset) => asset.name.endsWith('.pmtiles') && !canonicalAssetNames.has(asset.name))
.filter((asset) =>
asset.name.endsWith('.pmtiles') &&
!canonicalAssetNames.has(asset.name) &&
!virtualAssetNames.has(asset.name)
)
.map((asset) => ({ id: asset.id, name: asset.name, size: asset.size, updatedAt: asset.updated_at }));
const summary = {
capturedAt: new Date().toISOString(),
Expand Down
Loading
Loading