Skip to content

Commit

Permalink
Merge pull request #11 from juliangiebel/2023.08.30-map-tiling
Browse files Browse the repository at this point in the history
Implement map tiling
  • Loading branch information
juliangiebel authored Feb 14, 2024
2 parents 9b2357b + 5892cb4 commit 2216320
Show file tree
Hide file tree
Showing 12 changed files with 389 additions and 50 deletions.
6 changes: 3 additions & 3 deletions SS14.MapServer/Controllers/ImageController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,16 +134,16 @@ private async Task<IActionResult> InternalGetMap(Map? map, int gridId)
if (map == null)
return new NotFoundResult();

var hash = $@"""{map.MapGuid:N}{map.LastUpdated.GetHashCode():X}""";
var hash = $@"""{map.MapGuid:N}{gridId + map.LastUpdated.GetHashCode():X}""";
if (CheckETags(hash, out var result))
return result;

var grid = map.Grids.Find(value => value.GridId.Equals(gridId));
if (grid == null)
return new NotFoundResult();

if (grid.Tiled)
return new BadRequestObjectResult(new ApiErrorMessage($"Grid image with id {gridId} is a tiled image"));
//if (grid.Tiled)
// return new BadRequestObjectResult(new ApiErrorMessage($"Grid image with id {gridId} is a tiled image"));

if (!System.IO.File.Exists(grid.Path))
return new NotFoundResult();
Expand Down
3 changes: 3 additions & 0 deletions SS14.MapServer/Controllers/MapController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,9 @@ private Map SetMapGridUrls(Map map)
_serverConfiguration.Host.Scheme,
$"{_serverConfiguration.Host.Host}:{_serverConfiguration.Host.Port}{_serverConfiguration.PathBase}"
);

if (grid.Tiled)
grid.Url = grid.Url?.Replace("Image/grid", "Tile");
}
map.Grids.Sort((grid, grid1) => grid1.Extent.CompareTo(grid.Extent));
return map;
Expand Down
44 changes: 30 additions & 14 deletions SS14.MapServer/Controllers/TileController.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Net.Http.Headers;
using MimeTypes;
using SS14.MapServer.Exceptions;
using SS14.MapServer.Models;
using SS14.MapServer.Models.Entities;

namespace SS14.MapServer.Controllers;

[Route("api/[controller]")]
[Microsoft.AspNetCore.Mvc.Route("api/[controller]")]
[ApiController]
public class TileController : ControllerBase
{
Expand All @@ -21,42 +24,55 @@ public TileController(Context context)
}

[AllowAnonymous]
[ResponseCache(CacheProfileName = "Default")]
[DisableRateLimiting]
[ResponseCache(CacheProfileName = "Default", VaryByQueryKeys = new[] { "preload" })]
[HttpGet("{id:guid}/{gridId:int}/{x:int}/{y:int}/{z:int}")]
[ProducesResponseType(200, Type = typeof(FileStreamResult))]
[Produces("image/jpg", "image/png", "image/webp", "application/json")]
public async Task<IActionResult> GetTile(Guid id, int gridId, int x, int y, int z)
public async Task<IActionResult> GetTile(Guid id, int gridId, int x, int y, int z, [FromQuery] bool preload)
{
var map = await _context.Map!
.Include(map => map.Grids)
.Where(map => map.MapGuid.Equals(id))
.SingleOrDefaultAsync();
// TODO cache the result of this method
var (map, grid) = await RetrieveMapAndGrid(id, gridId);

if (map == null)
if (map == null || grid == null)
return new NotFoundResult();

var hash = $@"""{map.MapGuid:N}{map.LastUpdated.GetHashCode():X}""";
var hash = $@"""{map.MapGuid:N}{Convert.ToInt32(preload) + x + y + gridId + map.LastUpdated.GetHashCode():X}""";
if (CheckETags(hash, out var result))
return result;
var grid = map.Grids.Find(value => value.GridId.Equals(gridId));
if (grid == null)
return new NotFoundResult();
if (!grid.Tiled)
return new BadRequestObjectResult(new ApiErrorMessage($"Grid image with id {gridId} doesn't support image tiling"));

var tile = await _context.Tile!.FindAsync(id, gridId, x, y);

if (tile == null || !System.IO.File.Exists(grid.Path))
return new NotFoundResult();
if (tile == null || !System.IO.File.Exists(tile.Path))
return new OkResult();

if (preload)
return File(tile.Preview, "image/webp", true);

var file = new FileStream(tile.Path, FileMode.Open);
var mimeType = MimeTypeMap.GetMimeType(Path.GetExtension(tile.Path));

return File(file, mimeType, map.LastUpdated, new EntityTagHeaderValue(hash), true);
}

private async Task<(Map? map, Grid? grid)> RetrieveMapAndGrid(Guid id, int gridId)
{
var map = await _context.Map!
.Include(map => map.Grids)
.Where(map => map.MapGuid.Equals(id))
.SingleOrDefaultAsync();

if (map == null)
return (null, null);

var grid = map.Grids.Find(value => value.GridId.Equals(gridId));
return (map, grid);
}

private bool CheckETags(string hash, [NotNullWhen(true)] out IActionResult? result)
{
if (Request.Headers.IfNoneMatch.Any(h => h != null && h.Equals(hash)))
Expand Down
20 changes: 18 additions & 2 deletions SS14.MapServer/MapProcessing/Services/ImageProcessingService.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Formats.Webp;
using SS14.MapServer.Models.Entities;

namespace SS14.MapServer.MapProcessing.Services;
Expand All @@ -12,7 +13,7 @@ public sealed class ImageProcessingService
public async Task<List<Tile>> TileImage(Guid mapGuid, int gridId, string sourcePath, string targetPath, int tileSize)
{
if (tileSize < MinTileSize)
throw new ArgumentOutOfRangeException($"Provider tile size {tileSize} is smaller than minimum tile size {MinTileSize}");
throw new ArgumentOutOfRangeException($"Provided tile size {tileSize} is smaller than minimum tile size {MinTileSize}");

if (!Path.HasExtension(sourcePath))
throw new Exception($"Invalid image path: {sourcePath}");
Expand All @@ -30,17 +31,32 @@ public async Task<List<Tile>> TileImage(Guid mapGuid, int gridId, string sourceP
var extension = Path.GetExtension(sourcePath);
var encoder = image.DetectEncoder(sourcePath);

var compressedWebpEncoder = new WebpEncoder
{
Method = WebpEncodingMethod.Level6,
FileFormat = WebpFileFormatType.Lossy,
Quality = 1,
SkipMetadata = true,
FilterStrength = 0,
TransparentColorMode = WebpTransparentColorMode.Preserve
};

for (var y = 0; y < heightSteps; y++)
{
for (var x = 0; x < widthSteps; x++)
{
var rectangle = new Rectangle(tileSize * x, tileSize * y, tileSize, tileSize);
var tile = image.Clone(img => img.Crop(rectangle));
var preview = tile.Clone(img => img.Pixelate(8));

var path = Path.Combine(targetPath, $"tile_{Guid.NewGuid()}{extension}");
await tile.SaveAsync(path, encoder);

tiles.Add(new Tile(mapGuid, gridId, x, y, tileSize, path));
using var stream = new MemoryStream();
await preview.SaveAsync(stream, compressedWebpEncoder);
var previewData = stream.ToArray();

tiles.Add(new Tile(mapGuid, gridId, x, y, tileSize, path, previewData));
}
}

Expand Down
21 changes: 9 additions & 12 deletions SS14.MapServer/MapProcessing/Services/MapReaderServiceService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ namespace SS14.MapServer.MapProcessing.Services;
public sealed class MapReaderServiceService : IMapReaderService
{
private readonly BuildConfiguration _buildConfiguration = new();
private readonly GitConfiguration _gitConfiguration = new();
private readonly FileUploadService _fileUploadService;
private readonly Context _context;

Expand All @@ -22,9 +23,10 @@ public MapReaderServiceService(IConfiguration configuration, FileUploadService f
_context = context;

configuration.Bind(BuildConfiguration.Name, _buildConfiguration);
configuration.Bind(GitConfiguration.Name, _gitConfiguration);
}

public async Task<IList<Guid>> UpdateMapsFromFs(string path, string gitRef = "master", bool forceTiled = false, CancellationToken cancellationToken = default)
public async Task<IList<Guid>> UpdateMapsFromFs(string path, string gitRef, bool forceTiled = false, CancellationToken cancellationToken = default)
{
if (!Directory.Exists(path))
throw new DirectoryNotFoundException($"Map import path not found: {path}");
Expand Down Expand Up @@ -69,6 +71,9 @@ public async Task<IList<Guid>> UpdateMapsFromFs(string path, string gitRef = "ma
map.Attribution = data.Attribution;
map.ParallaxLayers = data.ParallaxLayers;

if (newMap)
await _context.Map!.AddAsync(map, cancellationToken);

//Remove previous grids if there are any
if (map.Grids.Count > 0)
_context.RemoveRange(map.Grids);
Expand All @@ -93,7 +98,8 @@ public async Task<IList<Guid>> UpdateMapsFromFs(string path, string gitRef = "ma
GridId = gridData.GridId,
Extent = gridData.Extent,
Offset = gridData.Offset,
Tiled = forceTiled || gridData.Tiled,
//Only tile maps used by the viewer and prevent small grids from being tiled
Tiled = forceTiled || gridData.Extent.GetArea() >= 65536 && gitRef == _gitConfiguration.Branch
};
map.Grids.Add(grid);
_context.Add(grid);
Expand All @@ -106,16 +112,7 @@ public async Task<IList<Guid>> UpdateMapsFromFs(string path, string gitRef = "ma
await stream.DisposeAsync();
}

if (newMap)
{
var id = (await _context.Map.AddAsync(map, cancellationToken)).Entity.MapGuid;
ids.Add(id);
}
else
{
ids.Add(map.MapGuid);
}

ids.Add(map.MapGuid);
await _context.SaveChangesAsync(cancellationToken);
}

Expand Down
Loading

0 comments on commit 2216320

Please sign in to comment.