using EarthQuake.Core;
using EarthQuake.Map.Tiles;
using EarthQuake.Map.Tiles.Vector;
using PMTiles;
using SkiaSharp;
using VectorTiles.Styles;
namespace EarthQuake.Map.Layers;
///
/// ベクトルタイルを描画するレイヤー
///
///
public class VectorMapLayer(VectorMapStyle? styles, string land) : CacheableLayer
{
protected internal VectorTilesController? Controller;
protected internal Header? Header;
private TilePoint _point;
private int _previousZoom;
protected internal readonly string Land = land;
public override void Render(SKCanvas canvas, float scale, SKRect bounds)
{
if (Controller is null || styles is null) return;
var origin = GeomTransform.TranslateToNonTransform(bounds.Left, bounds.Top);
VectorTilesController.GetXyzTile(origin,
Math.Min(Header?.MaxZoom ?? 15, Math.Max(Header?.MinZoom ?? 5, (int)Math.Log2(scale) + 5)), out var point);
VectorTilesController.GetXyzTileFromLatLon(Header?.MinLon ?? 0, Header?.MaxLat ?? 0, point.Z, out var leftTop);
VectorTilesController.GetXyzTileFromLatLon(Header?.MaxLon ?? 0, Header?.MinLat ?? 0, point.Z,
out var rightBottom);
var zoom = (int)Math.Pow(2, point.Z);
var h = (int)Math.Ceiling(bounds.Height / GeomTransform.Zoom / (GeomTransform.Height * 2f / zoom));
var w = (int)Math.Ceiling(bounds.Width / GeomTransform.Zoom / (360f / zoom));
h = Math.Min(h, zoom - point.Y);
w = Math.Min(w, zoom - point.X);
using var paint = new SKPaint();
paint.IsAntialias = true;
paint.Typeface = Font;
var widthFactor = point.Z switch
{
< 8 => 1,
< 10 => 2,
< 12 => 8,
_ => 14
}; // ズームするほど太くなっていっちゃうから調整
List features = [];
List rects = [];
for (var j = 0; j <= h; j++)
{
for (var i = 0; i <= w; i++)
{
var currentPoint = point.Add(i, j);
if (currentPoint.X < leftTop.X || currentPoint.Y < leftTop.Y || currentPoint.X > rightBottom.X ||
currentPoint.Y > rightBottom.Y) continue;
if (!Controller.TryGetTile(currentPoint, out var tile) || tile?.Vertices is null) continue;
features.AddRange(tile.Vertices);
}
}
foreach (var styleLayer in styles.Layers)
{
foreach (var feature in features)
{
if (feature.Layer?.Id != Land && ReferenceEquals(feature.Layer, styleLayer))
{
DrawLayer(canvas, scale, feature, paint, point, widthFactor, rects);
}
}
}
}
private protected static void DrawLayer(SKCanvas canvas, float scale, VectorTileFeature feature, SKPaint paint,
TilePoint point, int widthFactor, List rects)
{
switch (feature)
{
case VectorLineFeature line:
{
if (feature.Layer is not VectorLineStyleLayer layer) return;
paint.Color = layer.LineColor is null
? SKColors.White
: layer.LineColor.GetValue(feature.Tags)!.ToColor().ToSKColor();
paint.StrokeWidth =
(layer.LineWidth is null ? 1 : layer.LineWidth.GetValue(feature.Tags)!.ToFloat()) /
(point.Z > 12 ? 5000f : scale) / widthFactor;
paint.StrokeCap = SKStrokeCap.Round;
paint.StrokeJoin = SKStrokeJoin.Round;
paint.Style = SKPaintStyle.Stroke;
if (layer.DashArray is not null)
{
using var pathEffect =
SKPathEffect.CreateDash(layer.DashArray.Select(x => x / scale).ToArray(), 1);
paint.PathEffect = pathEffect;
}
else
{
paint.PathEffect = null;
}
canvas.DrawPath(line.Path, paint);
break;
}
case VectorFillFeature fill:
{
if (feature.Layer is not VectorFillStyleLayer layer) return;
if (fill.Vertices is null) return;
paint.Color = layer.FillColor is null
? SKColors.White
: layer.FillColor.GetValue(feature.Tags)!.ToColor().ToSKColor();
paint.Style = SKPaintStyle.Fill;
canvas.DrawVertices(fill.Vertices, SKBlendMode.Src, paint);
break;
}
case VectorSymbolFeature symbol:
{
if (feature.Layer is not VectorSymbolStyleLayer layer || symbol.Text is null) return;
paint.TextSize = (layer.TextSize is null ? 15 : layer.TextSize.GetValue(feature.Tags)!.ToFloat()) /
scale;
paint.Color = layer.TextColor is null
? SKColors.White
: layer.TextColor!.GetValue(feature.Tags)!.ToColor().ToSKColor();
paint.Style = SKPaintStyle.Fill;
var rect = new SKRect(symbol.Point.X, symbol.Point.Y - paint.TextSize,
symbol.Point.X + paint.MeasureText(symbol.Text), symbol.Point.Y);
if (rects.Any(x => x.IntersectsWith(rect))) return; // 重なっていたら描画しない
rects.Add(rect);
canvas.DrawText(symbol.Text, symbol.Point, paint);
break;
}
}
}
private protected override void Initialize()
{
Controller = new VectorTilesController(styles!)
{
OnUpdate = HandleUpdated
};
if (Controller.PMTiles != null) Header = Controller.PMTiles.GetHeader();
}
public override bool ShouldReload(float zoom, SKRect bounds)
{
var origin = GeomTransform.TranslateToNonTransform(bounds.MidX, bounds.MidY);
var zoomI = (int)Math.Log2(zoom) + 5;
VectorTilesController.GetXyzTile(origin, zoomI, out var point);
// 表示範囲のタイルが変わったか
if (_point == point && zoomI == _previousZoom) return false;
_previousZoom = zoomI;
_point = point;
return true;
}
}