From 9b2b785deaa87c94f1ec2254b91c1ed8993a72f3 Mon Sep 17 00:00:00 2001 From: Wentao Kuang Date: Tue, 1 Oct 2024 12:30:13 +1300 Subject: [PATCH 1/3] fix(cli): Install server package in the cli container to inlcude lerc. (#3353) ### Motivation The [cli container](https://github.com/linz/basemaps-config/actions/runs/11096451992/job/30827033370?pr=984) requires lerc to be installed which come from server. ` node:internal/modules/cjs/loader:1228 throw err; ^ Error: Cannot find module 'lerc' Require stack: - /app/index.cjs at Module._resolveFilename (node:internal/modules/cjs/loader:1225:15) at Module._load (node:internal/modules/cjs/loader:1051:27) at Module.require (node:internal/modules/cjs/loader:1311:19) at require (node:internal/modules/helpers:179:18) at Object. (/app/index.cjs:190419:27) at Module._compile (node:internal/modules/cjs/loader:1469:14) at Module._extensions..js (node:internal/modules/cjs/loader:1548:10) at Module.load (node:internal/modules/cjs/loader:1288:32) at Module._load (node:internal/modules/cjs/loader:1104:12) at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:[174](https://github.com/linz/basemaps-config/actions/runs/11096451992/job/30827033370?pr=984#step:6:175):12) { code: 'MODULE_NOT_FOUND', requireStack: [ '/app/index.cjs' ] } ` ### Modifications Install serve package in the cli container. ### Verification Container fixed after install server package, add smoke test in the container ci/cd for validation --- .github/workflows/containers.yml | 28 +++++++++++++++++++++++++++- packages/cli/Dockerfile | 3 ++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/.github/workflows/containers.yml b/.github/workflows/containers.yml index baee7a851..3e694aa3b 100644 --- a/.github/workflows/containers.yml +++ b/.github/workflows/containers.yml @@ -56,6 +56,19 @@ jobs: - name: Log in to registry run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin + - name: '@basemaps/cli - Build and export to Docker' + uses: docker/build-push-action@v5 + with: + context: packages/cli + load: true + tags: | + ghcr.io/linz/basemaps/cli:latest + ghcr.io/linz/basemaps/cli:${{ steps.version.outputs.version }} + + - name: '@basemaps/cli - Test' + run: | + docker run --rm ghcr.io/linz/basemaps/cli:${{ steps.version.outputs.version }} --help + - name: '@basemaps/cli - Build and push' uses: docker/build-push-action@v5 with: @@ -77,9 +90,21 @@ jobs: ghcr.io/linz/basemaps/cli:${{ steps.version.outputs.version_major }} ghcr.io/linz/basemaps/cli:${{ steps.version.outputs.version_major_minor }} ghcr.io/linz/basemaps/cli:${{ steps.version.outputs.version }} - push: ${{github.ref == 'refs/heads/master' && startsWith(github.event.head_commit.message, 'release:')}} + - name: '@basemaps/server - Build and export to Docker' + uses: docker/build-push-action@v5 + with: + context: packages/server + load: true + tags: | + ghcr.io/linz/basemaps/server:latest + ghcr.io/linz/basemaps/server:${{ steps.version.outputs.version }} + + - name: '@basemaps/server - Test' + run: | + docker run --rm ghcr.io/linz/basemaps/server:${{ steps.version.outputs.version }} --version + - name: '@basemaps/server - Build and push' uses: docker/build-push-action@v5 with: @@ -102,3 +127,4 @@ jobs: ghcr.io/linz/basemaps/server:${{ steps.version.outputs.version_major_minor }} ghcr.io/linz/basemaps/server:${{ steps.version.outputs.version }} push: ${{github.ref == 'refs/heads/master' && startsWith(github.event.head_commit.message, 'release:')}} + diff --git a/packages/cli/Dockerfile b/packages/cli/Dockerfile index 3ba8ea493..c436f6e51 100644 --- a/packages/cli/Dockerfile +++ b/packages/cli/Dockerfile @@ -21,11 +21,12 @@ RUN npm install sharp@0.33.0 COPY ./basemaps-landing*.tgz /app/ COPY ./basemaps-cogify*.tgz /app/ COPY ./basemaps-smoke*.tgz /app/ +COPY ./basemaps-server*.tgz /app/ # Copy the static files for v1/health check COPY ./static/ /app/static/ -RUN npm install ./basemaps-landing*.tgz ./basemaps-cogify*.tgz ./basemaps-smoke*.tgz +RUN npm install ./basemaps-landing*.tgz ./basemaps-cogify*.tgz ./basemaps-smoke*.tgz ./basemaps-server*.tgz COPY dist/index.cjs /app/ From c156391994685f7e342a48fd24041d480b598037 Mon Sep 17 00:00:00 2001 From: Wentao Kuang Date: Wed, 2 Oct 2024 10:41:18 +1300 Subject: [PATCH 2/3] release: v7.11.1 (#3355) ### Motivation ### Modifications ### Verification --- CHANGELOG.md | 11 +++++++++++ lerna.json | 2 +- package-lock.json | 10 +++++----- packages/bathymetry/CHANGELOG.md | 8 ++++++++ packages/bathymetry/package.json | 4 ++-- packages/cli/CHANGELOG.md | 11 +++++++++++ packages/cli/package.json | 2 +- packages/cogify/CHANGELOG.md | 8 ++++++++ packages/cogify/package.json | 4 ++-- 9 files changed, 49 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fc32345c..5415bddaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [7.11.1](https://github.com/linz/basemaps/compare/v7.11.0...v7.11.1) (2024-10-01) + + +### Bug Fixes + +* **cli:** Install server package in the cli container to inlcude lerc. ([#3353](https://github.com/linz/basemaps/issues/3353)) ([9b2b785](https://github.com/linz/basemaps/commit/9b2b785deaa87c94f1ec2254b91c1ed8993a72f3)) + + + + + # [7.11.0](https://github.com/linz/basemaps/compare/v7.10.0...v7.11.0) (2024-09-29) diff --git a/lerna.json b/lerna.json index 4f03dc8ea..d190accd5 100644 --- a/lerna.json +++ b/lerna.json @@ -7,5 +7,5 @@ "conventionalCommits": true } }, - "version": "7.11.0" + "version": "7.11.1" } diff --git a/package-lock.json b/package-lock.json index 0a993b13c..814a2aa99 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19319,10 +19319,10 @@ }, "packages/bathymetry": { "name": "@basemaps/bathymetry", - "version": "7.11.0", + "version": "7.11.1", "license": "MIT", "dependencies": { - "@basemaps/cli": "^7.11.0", + "@basemaps/cli": "^7.11.1", "@basemaps/geo": "^7.11.0", "@basemaps/shared": "^7.11.0", "@rushstack/ts-command-line": "^4.3.13", @@ -19347,7 +19347,7 @@ }, "packages/cli": { "name": "@basemaps/cli", - "version": "7.11.0", + "version": "7.11.1", "license": "MIT", "dependencies": { "@basemaps/config": "^7.11.0", @@ -19410,13 +19410,13 @@ }, "packages/cogify": { "name": "@basemaps/cogify", - "version": "7.11.0", + "version": "7.11.1", "license": "MIT", "bin": { "cogify": "build/bin.js" }, "devDependencies": { - "@basemaps/cli": "^7.11.0", + "@basemaps/cli": "^7.11.1", "@basemaps/config": "^7.11.0", "@basemaps/config-loader": "^7.11.0", "@basemaps/geo": "^7.11.0", diff --git a/packages/bathymetry/CHANGELOG.md b/packages/bathymetry/CHANGELOG.md index 4310caac5..6fc9894ea 100644 --- a/packages/bathymetry/CHANGELOG.md +++ b/packages/bathymetry/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [7.11.1](https://github.com/linz/basemaps/compare/v7.11.0...v7.11.1) (2024-10-01) + +**Note:** Version bump only for package @basemaps/bathymetry + + + + + # [7.11.0](https://github.com/linz/basemaps/compare/v7.10.0...v7.11.0) (2024-09-29) **Note:** Version bump only for package @basemaps/bathymetry diff --git a/packages/bathymetry/package.json b/packages/bathymetry/package.json index 3f8a8040e..2bbb3a39e 100644 --- a/packages/bathymetry/package.json +++ b/packages/bathymetry/package.json @@ -1,6 +1,6 @@ { "name": "@basemaps/bathymetry", - "version": "7.11.0", + "version": "7.11.1", "repository": { "type": "git", "url": "https://github.com/linz/basemaps.git", @@ -28,7 +28,7 @@ "build/" ], "dependencies": { - "@basemaps/cli": "^7.11.0", + "@basemaps/cli": "^7.11.1", "@basemaps/geo": "^7.11.0", "@basemaps/shared": "^7.11.0", "@rushstack/ts-command-line": "^4.3.13", diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 7635af908..83fe1f92a 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [7.11.1](https://github.com/linz/basemaps/compare/v7.11.0...v7.11.1) (2024-10-01) + + +### Bug Fixes + +* **cli:** Install server package in the cli container to inlcude lerc. ([#3353](https://github.com/linz/basemaps/issues/3353)) ([9b2b785](https://github.com/linz/basemaps/commit/9b2b785deaa87c94f1ec2254b91c1ed8993a72f3)) + + + + + # [7.11.0](https://github.com/linz/basemaps/compare/v7.10.0...v7.11.0) (2024-09-29) diff --git a/packages/cli/package.json b/packages/cli/package.json index 8f28e4837..3e8715fc7 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@basemaps/cli", - "version": "7.11.0", + "version": "7.11.1", "private": false, "repository": { "type": "git", diff --git a/packages/cogify/CHANGELOG.md b/packages/cogify/CHANGELOG.md index 0a34dd6fc..892033caa 100644 --- a/packages/cogify/CHANGELOG.md +++ b/packages/cogify/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [7.11.1](https://github.com/linz/basemaps/compare/v7.11.0...v7.11.1) (2024-10-01) + +**Note:** Version bump only for package @basemaps/cogify + + + + + # [7.11.0](https://github.com/linz/basemaps/compare/v7.10.0...v7.11.0) (2024-09-29) **Note:** Version bump only for package @basemaps/cogify diff --git a/packages/cogify/package.json b/packages/cogify/package.json index b3ae873d0..723d790dd 100644 --- a/packages/cogify/package.json +++ b/packages/cogify/package.json @@ -1,6 +1,6 @@ { "name": "@basemaps/cogify", - "version": "7.11.0", + "version": "7.11.1", "private": false, "repository": { "type": "git", @@ -40,7 +40,7 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "devDependencies": { - "@basemaps/cli": "^7.11.0", + "@basemaps/cli": "^7.11.1", "@basemaps/config": "^7.11.0", "@basemaps/config-loader": "^7.11.0", "@basemaps/geo": "^7.11.0", From 5b207de92b76e0d445a41ef8e1e9b9b91e5363c6 Mon Sep 17 00:00:00 2001 From: Tawera Manaena Date: Mon, 7 Oct 2024 14:57:28 +1300 Subject: [PATCH 3/3] feat(server): add redirect route to pre-zoomed tileset BM-1076 (#3354) ### Motivation As an Imagery Data Maintainer, I want a link for each tileset that auto-zooms to its bounding box. This is so I can keep _Basemaps_ URL links short, clean looking, and zoomed to the right spot. ### Modifications 1. Added a `/v1/link/:tileSet` route to the `basemaps/lambda-tiler` package. 2. Implemented a function to capture and process requests to `v1/link/:tileSet`. - On success, returns a `302` response that redirects the client to a _Basemaps_ URL that is already zoomed to the extent of the tileset's imagery. - On failure, returns a `4xx` response explaining why the function terminated. #### Usage | Enters from... | Status | Redirects to... | |-|-|-| | `/v1/link/ashburton-2023-0.1m` | 302 Found | `/@-43.9157018,171.7712402,z12?i=ashburton-2023-0.1m` | ### Verification Depending on the size of the user's viewport, there are situations where the _pre-zooming_ estimation may or may not suffice. See each of the following examples for details: #### Ashburton 0.1m (2023) | From | To | |-|-| | `/link/ashburton-2023-0.1m` | `/@-43.9157018,171.7712402,z12?i=ashburton-2023-0.1m` | || ![img_1] | The tileset is over-zoomed by a slight amount. But, it's not noticeable. #### Christchurch 0.05m (2021) | From | To | |-|-| | `/link/christchurch-urban-2021-0.05m` | `@-43.5286378,172.6309204,z12?i=christchurch-urban-2021-0.05m` | || ![img_2] | The tileset is under-zoomed quite substantially. It seems as though the bounding box itself is much larger than the imagery. The user will have to zoom in themselves. #### Otago 0.1m (2018) | From | To | |-|-| | `/link/otago-urban-2018-0 1m` | `/@-45.2516883,169.6289062,z10?i=otago-urban-2018-0.1m` | || ![img_3] | Regions of the tileset are cut off from the viewport. The user will have to zoom out themselves. [img_1]: https://github.com/user-attachments/assets/1ae960b6-e4d6-4f78-8512-1e45edd4dc41 [img_2]: https://github.com/user-attachments/assets/e51c74e0-4ab2-4255-930b-75bc53d5adcf [img_3]: https://github.com/user-attachments/assets/9e4835bc-296e-4f9a-aad0-3629adf7cc74 --- packages/lambda-tiler/src/index.ts | 4 + .../src/routes/__tests__/link.test.ts | 114 ++++++++++++++++++ packages/lambda-tiler/src/routes/link.ts | 55 +++++++++ 3 files changed, 173 insertions(+) create mode 100644 packages/lambda-tiler/src/routes/__tests__/link.test.ts create mode 100644 packages/lambda-tiler/src/routes/link.ts diff --git a/packages/lambda-tiler/src/index.ts b/packages/lambda-tiler/src/index.ts index 8c5257e7d..f14ffbd38 100644 --- a/packages/lambda-tiler/src/index.ts +++ b/packages/lambda-tiler/src/index.ts @@ -6,6 +6,7 @@ import { configImageryGet, configTileSetGet } from './routes/config.js'; import { fontGet, fontList } from './routes/fonts.js'; import { healthGet } from './routes/health.js'; import { imageryGet } from './routes/imagery.js'; +import { linkGet } from './routes/link.js'; import { pingGet } from './routes/ping.js'; import { previewIndexGet } from './routes/preview.index.js'; import { tilePreviewGet } from './routes/preview.js'; @@ -102,6 +103,9 @@ handler.router.get('/v1/preview/:tileSet/:tileMatrix/:z/:lon/:lat/:outputType', handler.router.get('/v1/@:location', previewIndexGet); handler.router.get('/@:location', previewIndexGet); +// Link +handler.router.get('/v1/link/:tileSet', linkGet); + // Attribution handler.router.get('/v1/tiles/:tileSet/:tileMatrix/attribution.json', tileAttributionGet); handler.router.get('/v1/attribution/:tileSet/:tileMatrix/summary.json', tileAttributionGet); diff --git a/packages/lambda-tiler/src/routes/__tests__/link.test.ts b/packages/lambda-tiler/src/routes/__tests__/link.test.ts new file mode 100644 index 000000000..81a7700a4 --- /dev/null +++ b/packages/lambda-tiler/src/routes/__tests__/link.test.ts @@ -0,0 +1,114 @@ +import { strictEqual } from 'node:assert'; +import { afterEach, describe, it } from 'node:test'; + +import { ConfigProviderMemory } from '@basemaps/config'; +import { Epsg } from '@basemaps/geo'; + +import { FakeData, Imagery3857 } from '../../__tests__/config.data.js'; +import { mockRequest } from '../../__tests__/xyz.util.js'; +import { handler } from '../../index.js'; +import { ConfigLoader } from '../../util/config.loader.js'; + +describe('/v1/link/:tileSet', () => { + const FakeTileSetName = 'tileset'; + const config = new ConfigProviderMemory(); + + afterEach(() => { + config.objects.clear(); + }); + + /** + * 3xx status responses + */ + + // tileset found, is raster type, has one layer, has '3857' entry, imagery found > 302 response + it('success: redirect to pre-zoomed imagery', async (t) => { + t.mock.method(ConfigLoader, 'getDefaultConfig', () => Promise.resolve(config)); + + config.put(FakeData.tileSetRaster(FakeTileSetName)); + config.put(Imagery3857); + + const req = mockRequest(`/v1/link/${FakeTileSetName}`); + const res = await handler.router.handle(req); + + strictEqual(res.status, 302); + strictEqual(res.statusDescription, 'Redirect to pre-zoomed imagery'); + }); + + /** + * 4xx status responses + */ + + // tileset not found > 404 response + it('failure: tileset not found', async (t) => { + t.mock.method(ConfigLoader, 'getDefaultConfig', () => Promise.resolve(config)); + + const req = mockRequest(`/v1/link/${FakeTileSetName}`); + const res = await handler.router.handle(req); + + strictEqual(res.status, 404); + strictEqual(res.statusDescription, 'Tileset not found'); + }); + + // tileset found, not raster type > 400 response + it('failure: tileset must be raster type', async (t) => { + t.mock.method(ConfigLoader, 'getDefaultConfig', () => Promise.resolve(config)); + + config.put(FakeData.tileSetVector(FakeTileSetName)); + + const req = mockRequest(`/v1/link/${FakeTileSetName}`); + const res = await handler.router.handle(req); + + strictEqual(res.status, 400); + strictEqual(res.statusDescription, 'Tileset must be raster type'); + }); + + // tileset found, is raster type, has more than one layer > 400 response + it('failure: too many layers', async (t) => { + t.mock.method(ConfigLoader, 'getDefaultConfig', () => Promise.resolve(config)); + + const tileSet = FakeData.tileSetRaster(FakeTileSetName); + + // add another layer + tileSet.layers.push(tileSet.layers[0]); + + config.put(tileSet); + + const req = mockRequest(`/v1/link/${FakeTileSetName}`); + const res = await handler.router.handle(req); + + strictEqual(res.status, 400); + strictEqual(res.statusDescription, 'Too many layers'); + }); + + // tileset found, is raster type, has one layer, no '3857' entry > 400 response + it("failure: no imagery for '3857' projection", async (t) => { + t.mock.method(ConfigLoader, 'getDefaultConfig', () => Promise.resolve(config)); + + const tileSet = FakeData.tileSetRaster(FakeTileSetName); + + // delete '3857' entry + delete tileSet.layers[0][Epsg.Google.code]; + + config.put(tileSet); + + const req = mockRequest(`/v1/link/${FakeTileSetName}`); + const res = await handler.router.handle(req); + + strictEqual(res.status, 400); + strictEqual(res.statusDescription, "No imagery for '3857' projection"); + }); + + // tileset found, is raster type, has one layer, has '3857' entry, imagery not found > 400 response + it('failure: imagery not found', async (t) => { + t.mock.method(ConfigLoader, 'getDefaultConfig', () => Promise.resolve(config)); + + config.put(FakeData.tileSetRaster(FakeTileSetName)); + + const req = mockRequest(`/v1/link/${FakeTileSetName}`); + const res = await handler.router.handle(req); + + strictEqual(res.status, 400); + strictEqual(res.statusDescription, 'Imagery not found'); + }); +}); diff --git a/packages/lambda-tiler/src/routes/link.ts b/packages/lambda-tiler/src/routes/link.ts new file mode 100644 index 000000000..6eb150d4e --- /dev/null +++ b/packages/lambda-tiler/src/routes/link.ts @@ -0,0 +1,55 @@ +import { TileSetType } from '@basemaps/config'; +import { Epsg } from '@basemaps/geo'; +import { getPreviewUrl } from '@basemaps/shared'; +import { LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda'; + +import { ConfigLoader } from '../util/config.loader.js'; + +export interface LinkGet { + Params: { + tileSet: string; + }; +} + +/** + * Redirect the client to a Basemaps URL that is already zoomed to the extent of the tileset's imagery. + * + * /v1/link/:tileSet + * + * @example + * '/v1/link/ashburton-2023-0.1m' + * + * @returns on success, 302 redirect response. on failure, 4xx status code response. + */ +export async function linkGet(req: LambdaHttpRequest): Promise { + const config = await ConfigLoader.load(req); + + // get tileset + + req.timer.start('tileset:load'); + const tileSet = await config.TileSet.get(req.params.tileSet); + req.timer.end('tileset:load'); + + if (tileSet == null) return new LambdaHttpResponse(404, 'Tileset not found'); + + if (tileSet.type !== TileSetType.Raster) return new LambdaHttpResponse(400, 'Tileset must be raster type'); + + // TODO: add support for 'aerial' and 'elevation' multi-layer tilesets + if (tileSet.layers.length !== 1) return new LambdaHttpResponse(400, 'Too many layers'); + + // get imagery + + const imageryId = tileSet.layers[0][Epsg.Google.code]; + if (imageryId === undefined) return new LambdaHttpResponse(400, "No imagery for '3857' projection"); + + const imagery = await config.Imagery.get(imageryId); + if (imagery == null) return new LambdaHttpResponse(400, 'Imagery not found'); + + // do redirect + + const url = getPreviewUrl({ imagery }); + + return new LambdaHttpResponse(302, 'Redirect to pre-zoomed imagery', { + location: `/${url.slug}?i=${url.name}`, + }); +}