Skip to content

Commit d322760

Browse files
committed
feat(xyz): implement XYZ tile controller with bordered WMTS cache
- Rewrite XYZ controller from commented-out skeleton to working implementation using IWMTSGrain with file-based caching and auth support - Thread Bordered parameter through WMTS pipeline to cache key computation - Update Docker image tag to 202606-1
1 parent 98ac5ff commit d322760

7 files changed

Lines changed: 117 additions & 76 deletions

File tree

.github/workflows/docker-image.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,6 @@ jobs:
1717
- name: Login docker regsitry
1818
run: docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_USER_PASSWORD }}
1919
- name: Build the Docker image
20-
run: docker build --target zserver-api -f API.Dockerfile -t zlzforever/zserver-api:202605-1 .
20+
run: docker build --target zserver-api -f API.Dockerfile -t zlzforever/zserver-api:202606-1.
2121
- name: Publish the Docker image
22-
run: docker push zlzforever/zserver-api:202605-1
22+
run: docker push zlzforever/zserver-api:202606-1

src/ZMap/Infrastructure/Utility.cs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,24 +30,23 @@ ______ _____
3030
/// <param name="tileMatrix"></param>
3131
/// <param name="tileRow"></param>
3232
/// <param name="tileCol"></param>
33+
/// <param name="bordered"></param>
3334
/// <returns></returns>
3435
public static PathInfo GetWmtsPath(string layers, string filter, string format,
3536
string tileMatrixSet,
3637
string tileMatrix, int tileRow,
37-
int tileCol)
38+
int tileCol, bool bordered)
3839
{
3940
// group1:layer1,layer2 -> group1_layer1.group2_layer2
4041
var layerKey = layers.Replace(',', '.').Replace(':', '_');
41-
var cqlFilterKey = string.IsNullOrWhiteSpace(filter)
42-
? string.Empty
43-
: CryptographyUtility.ComputeHash(Encoding.UTF8.GetBytes(filter));
42+
var group = CryptographyUtility.ComputeHash(Encoding.UTF8.GetBytes(filter + "_" + bordered));
4443

4544
var imageExtension = GetImageExtension(format);
4645

4746
var tileMatrixSetPath = tileMatrixSet.Replace(":", "").Replace("/", "_");
48-
var intervalPath = string.IsNullOrEmpty(cqlFilterKey)
47+
var intervalPath = string.IsNullOrEmpty(group)
4948
? $"wmts{Path.DirectorySeparatorChar}{layerKey}{Path.DirectorySeparatorChar}{tileMatrixSetPath}{Path.DirectorySeparatorChar}{tileMatrix}{Path.DirectorySeparatorChar}{tileRow}{Path.DirectorySeparatorChar}{tileCol}{imageExtension}"
50-
: $"wmts{Path.DirectorySeparatorChar}{layerKey}{Path.DirectorySeparatorChar}{tileMatrixSetPath}{Path.DirectorySeparatorChar}{tileMatrix}{Path.DirectorySeparatorChar}{tileRow}{Path.DirectorySeparatorChar}{tileCol}_{cqlFilterKey}{imageExtension}";
49+
: $"wmts{Path.DirectorySeparatorChar}{layerKey}{Path.DirectorySeparatorChar}{tileMatrixSetPath}{Path.DirectorySeparatorChar}{tileMatrix}{Path.DirectorySeparatorChar}{tileRow}{Path.DirectorySeparatorChar}{tileCol}_{group}{imageExtension}";
5150
var fullPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "cache", intervalPath);
5251
return new PathInfo(fullPath, intervalPath);
5352
}

src/ZMap/Ogc/Wmts/WmtsService.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ public async ValueTask<MapResult> GetTileAsync(string layers, string styles,
4848
$"Could not find tile matrix set {tileMatrixSet}");
4949
}
5050

51-
var tuple = Utility.GetWmtsPath(layers, zFilter, format, tileMatrixSet, tileMatrix, tileRow, tileCol);
51+
var bordered = arguments.TryGetValue("Bordered", out var b) && (bool)b;
52+
var tuple = Utility.GetWmtsPath(layers, zFilter, format, tileMatrixSet, tileMatrix, tileRow, tileCol,
53+
bordered);
5254
if (string.IsNullOrEmpty(tuple.FullPath))
5355
{
5456
displayUrl = GetTileDisplayUrl(traceIdentifier, layers, styles, format, tileMatrixSet, tileMatrix,
@@ -157,7 +159,7 @@ public async ValueTask<MapResult> GetTileAsync(string layers, string styles,
157159
}
158160

159161
var scale = gridSetEnvelope.ScaleDenominator;
160-
var bordered = arguments.TryGetValue("Bordered", out var b) && (bool)b;
162+
161163
var viewport = new Viewport
162164
{
163165
Tile = new Tile(tileMatrix, tileCol, tileRow),

src/ZServer.API/Controllers/WMTSController.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,12 @@ ILogger<WMTSController> logger
4646
public async Task GetAsync([Required] [FromQuery(Name = "layer"), StringLength(100)] string layers,
4747
[StringLength(255)] string style,
4848
[Required, StringLength(100)] string tileMatrix, [Required] int tileRow, [Required] int tileCol,
49-
[StringLength(25)]
50-
string format = "image/png",
49+
[StringLength(25)] string format = "image/png",
5150
[Required, StringLength(50)] string tileMatrixSet = "EPSG:4326",
5251
[FromQuery(Name = "Z_FILTER"), StringLength(2048)]
5352
string filter = null, bool bordered = false)
5453
{
55-
var tuple = Utility.GetWmtsPath(layers, filter, format, tileMatrixSet, tileMatrix, tileRow, tileCol);
54+
var tuple = Utility.GetWmtsPath(layers, filter, format, tileMatrixSet, tileMatrix, tileRow, tileCol, bordered);
5655

5756
logger.LogDebug("[{TraceIdentifier}] Request wmts service {TileMatrix} {TileMatrixSet} {TileCol} {TileRow}",
5857
HttpContext.TraceIdentifier, tileMatrix, tileMatrixSet, tileCol, tileRow);
Lines changed: 102 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,102 @@
1-
// using System.ComponentModel.DataAnnotations;
2-
// using System.Threading.Tasks;
3-
// using Microsoft.AspNetCore.Http.Extensions;
4-
// using Microsoft.AspNetCore.Mvc;
5-
// using Microsoft.Extensions.Logging;
6-
// using Orleans;
7-
// using SkiaSharp;
8-
// using ZMap.Source.CloudOptimizedGeoTIFF;
9-
// using ZServer.Store;
10-
//
11-
// namespace ZServer.API.Controllers;
12-
//
13-
// [ApiController]
14-
// [Microsoft.AspNetCore.Components.Route("[controller]")]
15-
// public class XyzController(ILogger<XyzController> logger, IClusterClient clusterClient, ISourceStore sourceStore)
16-
// : ControllerBase
17-
// {
18-
// [HttpGet("{layers}")]
19-
// public async Task<IActionResult> GetAsync([FromRoute, Required, StringLength(100)] string layers,
20-
// [FromQuery] int x, [FromQuery] int y, [FromQuery, StringLength(100)] string z,
21-
// [StringLength(20)] string format = "image/png")
22-
// {
23-
// var displayUrl = Request.GetDisplayUrl();
24-
// if (string.IsNullOrWhiteSpace(layers))
25-
// {
26-
// logger.LogError($"LAYERS should not be empty/null: {displayUrl}");
27-
// return NotFound();
28-
// }
29-
//
30-
// // var sourceList = layers.Split(',', StringSplitOptions.RemoveEmptyEntries);
31-
// // foreach (var source in sourceList)
32-
// // {
33-
// //
34-
// // }
35-
// var source = await sourceStore.FindAsync(layers);
36-
// if (source is COGGeoTiffSource cogSource)
37-
// {
38-
// var image = await cogSource.GetImageAsync(z, x, y);
39-
//
40-
// var skiaImage = SKImage.FromEncodedData(image);
41-
// var imageFormat = GetImageFormat(format);
42-
// await using var stream = skiaImage.Encode(imageFormat, 90).AsStream();
43-
// return File(stream, "image/png");
44-
// }
45-
//
46-
// return File([], "image/png");
47-
// }
48-
//
49-
// private SKEncodedImageFormat GetImageFormat(string format)
50-
// {
51-
// return format switch
52-
// {
53-
// "image/png" => SKEncodedImageFormat.Png,
54-
// "image/jpeg" => SKEncodedImageFormat.Jpeg,
55-
// "image/webp" => SKEncodedImageFormat.Webp,
56-
// "image/gif" => SKEncodedImageFormat.Gif,
57-
// "image/bmp" => SKEncodedImageFormat.Bmp,
58-
// _ => SKEncodedImageFormat.Png
59-
// };
60-
// }
61-
// }
1+
using System.Collections.Generic;
2+
using System.ComponentModel.DataAnnotations;
3+
using System.Threading.Tasks;
4+
using Microsoft.AspNetCore.Authorization;
5+
using Microsoft.AspNetCore.Mvc;
6+
using Microsoft.Extensions.Logging;
7+
using Orleans;
8+
using SkiaSharp;
9+
using ZMap;
10+
using ZMap.Infrastructure;
11+
using ZServer.Interfaces.WMTS;
12+
13+
namespace ZServer.API.Controllers;
14+
15+
/// <summary>
16+
/// WMS 服务
17+
/// </summary>
18+
/// <param name="clusterClient"></param>
19+
/// <param name="logger"></param>
20+
[ApiController]
21+
[Route("[controller]")]
22+
[Authorize(Policy = "default")]
23+
[Microsoft.AspNetCore.Components.Route("[controller]")]
24+
public class XyzController(ILogger<XyzController> logger, IClusterClient clusterClient)
25+
: ControllerBase
26+
{
27+
/// <summary>
28+
/// 90% xyz 都是默认 3857
29+
/// </summary>
30+
/// <param name="layers"></param>
31+
/// <param name="x"></param>
32+
/// <param name="y"></param>
33+
/// <param name="z"></param>
34+
/// <param name="filter"></param>
35+
/// <param name="format"></param>
36+
/// <param name="tileMatrixSet"></param>
37+
/// <param name="style"></param>
38+
/// <param name="bordered"></param>
39+
[HttpGet("{layers}")]
40+
public async Task GetAsync([FromRoute, Required, StringLength(100)] string layers,
41+
[FromQuery] int x, [FromQuery] int y, [FromQuery, StringLength(100)] string z,
42+
[FromQuery(Name = "Z_FILTER"), StringLength(2048)]
43+
string filter = null,
44+
[StringLength(20)] string format = "image/png", [StringLength(12)] string tileMatrixSet = "3857",
45+
[StringLength(255)] string style = null, bool bordered = false)
46+
{
47+
tileMatrixSet = $"EPSG:{tileMatrixSet}";
48+
var tileMatrix = z;
49+
var tileCol = x;
50+
var tileRow = y;
51+
var tuple = Utility.GetWmtsPath(layers, filter, format, tileMatrixSet, tileMatrix, tileRow, tileCol, bordered);
52+
53+
logger.LogDebug("[{TraceIdentifier}] Request wmts service {TileMatrix} {TileMatrixSet} {TileCol} {TileRow}",
54+
HttpContext.TraceIdentifier, tileMatrix, tileMatrixSet, tileCol, tileRow);
55+
56+
#if !DEBUG
57+
if (System.IO.File.Exists(tuple.FullPath))
58+
{
59+
if (EnvironmentVariables.EnableSensitiveDataLogging)
60+
{
61+
var displayUrl =
62+
$"[{HttpContext.TraceIdentifier}] LAYERS={layers}&STYLES={style}&FORMAT={format}&TILEMATRIXSET={tileMatrixSet}&TILEMATRIX={tileMatrix}&TILEROW={tileRow}&TILECOL={tileCol}";
63+
logger.LogInformation("{Service} [{TraceIdentifier}] {Url}, CACHED", "XYZ", HttpContext.TraceIdentifier,
64+
displayUrl);
65+
}
66+
67+
await using var stream = System.IO.File.OpenRead(tuple.FullPath);
68+
HttpContext.Response.ContentType = format;
69+
HttpContext.Response.ContentLength = stream.Length;
70+
await stream.CopyToAsync(HttpContext.Response.Body, (int)stream.Length);
71+
return;
72+
}
73+
#endif
74+
75+
// 同一个 Grid 使用同一个对象进行管理, 保证缓存文件在同一个 Silo 目录下
76+
var grain = clusterClient.GetGrain<IWMTSGrain>(tuple.IntervalPath);
77+
78+
var result =
79+
await grain.GetTileAsync(layers, style, format, tileMatrixSet, tileMatrix, tileRow, tileCol,
80+
filter,
81+
new Dictionary<string, object>
82+
{
83+
{ Defaults.TraceIdentifier, HttpContext.TraceIdentifier },
84+
{ "Bordered", bordered }
85+
});
86+
87+
await HttpContext.WriteZServerResponseAsync(result);
88+
}
89+
90+
private SKEncodedImageFormat GetImageFormat(string format)
91+
{
92+
return format switch
93+
{
94+
"image/png" => SKEncodedImageFormat.Png,
95+
"image/jpeg" => SKEncodedImageFormat.Jpeg,
96+
"image/webp" => SKEncodedImageFormat.Webp,
97+
"image/gif" => SKEncodedImageFormat.Gif,
98+
"image/bmp" => SKEncodedImageFormat.Bmp,
99+
_ => SKEncodedImageFormat.Png
100+
};
101+
}
102+
}

src/ZServer.Tests/BaseTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ static BaseTests()
3434
serviceCollection.TryAddSingleton<RefreshConfigService>();
3535
Service = serviceCollection.BuildServiceProvider();
3636
var syncService = Service.GetRequiredService<RefreshConfigService>();
37-
var configurationProvider = Service.GetRequiredService<FileJsonStoreProvider>();
37+
var configurationProvider = Service.GetRequiredService<IJsonStoreProvider>();
3838

3939
syncService.RefreshAsync(configurationProvider, NullLogger.Instance).GetAwaiter().GetResult();
4040
}

src/ZServer.Tests/UtilityTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ public class UtilityTests
88
[Fact]
99
public void GetWmtsPath()
1010
{
11-
var b = Utility.GetWmtsPath("abcd", "Equal", "image/png", "ESPG:4326", "14", 1, 1);
11+
var b = Utility.GetWmtsPath("abcd", "Equal", "image/png", "ESPG:4326", "14", 1, 1, true);
1212
Assert.Equal("wmts/abcd/ESPG4326/14/1/1_F5F286E73BDA105E538310B3190F75C5.png", b.IntervalPath);
1313
}
1414
}

0 commit comments

Comments
 (0)