diff --git a/RobotApp.Client/Pages/Components/Mapping/MapView.razor b/RobotApp.Client/Pages/Components/Mapping/MapView.razor
index df2d737..86aaa64 100644
--- a/RobotApp.Client/Pages/Components/Mapping/MapView.razor
+++ b/RobotApp.Client/Pages/Components/Mapping/MapView.razor
@@ -1,8 +1,25 @@
@inject IJSRuntime JS
+@inject HttpClient Http
@using Excubo.Blazor.Canvas.Contexts
+
+
+
+
+
+
+
+
+
+
-
-
@@ -56,6 +84,20 @@
private const double RulerHeight = 20;
+ private const double RobotWidth = 0.606;
+ private const double RobotLength = 1.106;
+
+ private bool RobotImageLoaded = false;
+ private bool MapImageLoaded = false;
+
+ private const double ImageX = -10;
+ private const double ImageY = -10;
+ private const double ImageResolution = 0.05;
+
+ private double MapImageWidth = 0;
+ private double MapImageHeight = 0;
+ private const string MAP_CACHE_KEY = "map_image";
+
protected override async Task OnAfterRenderAsync(bool first_render)
{
await base.OnAfterRenderAsync(first_render);
@@ -71,9 +113,45 @@
CanvasTranslateX = CanvasWidth / 2;
CanvasTranslateY = CanvasHeight / 2;
+ await LoadRobotImage();
+ await LoadMapImage();
+
await DrawCanvas();
}
+ private async Task LoadRobotImage()
+ {
+ try
+ {
+ await JS.InvokeVoidAsync("preloadImage", "images/AMR-250.png");
+ RobotImageLoaded = true;
+ }
+ catch
+ {
+ RobotImageLoaded = false;
+ }
+ }
+
+ private async Task LoadMapImage()
+ {
+ try
+ {
+ MapImageLoaded = false;
+ string baseUrl = Http.BaseAddress?.ToString() ?? "";
+ string apiUrl = $"{baseUrl}api/images/map";
+ await JS.InvokeVoidAsync("preloadImageFromUrl", apiUrl, MAP_CACHE_KEY);
+
+ var imageDimensions = await JS.InvokeAsync
("getImageDimensions", MAP_CACHE_KEY);
+ MapImageWidth = imageDimensions.Width * ImageResolution;
+ MapImageHeight = imageDimensions.Height * ImageResolution;
+ if (MapImageWidth > 0 && MapImageHeight > 0) MapImageLoaded = true;
+ }
+ catch
+ {
+ MapImageLoaded = false;
+ }
+ }
+
private async Task DrawCanvas()
{
await using var ctx = await JS.GetContext2DAsync(CanvasRef);
@@ -86,25 +164,202 @@
await ctx.TranslateAsync(CanvasTranslateX, CanvasTranslateY);
await ctx.ScaleAsync(ZoomScale, ZoomScale);
+
+ await DrawMapImage(ctx);
await DrawGrid(ctx);
await DrawAxes(ctx);
-
+ await DrawLaserScannerPoints(ctx);
await ctx.RestoreAsync();
- // Draw mouse indicator (outside transform)
if (IsMouseInCanvas)
{
await DrawMouseIndicator(ctx);
}
}
+ private async Task DrawMapImage(Context2D ctx)
+ {
+ if (!MapImageLoaded)
+ {
+ return;
+ }
+
+ await ctx.SaveAsync();
+
+ try
+ {
+
+ double imageWidthCanvas = MapImageWidth * BASE_PIXELS_PER_METER;
+ double imageHeightCanvas = MapImageHeight * BASE_PIXELS_PER_METER;
+
+ double mapCanvasX = ImageX * BASE_PIXELS_PER_METER;
+ double mapCanvasY = (ImageY + MapImageHeight) * BASE_PIXELS_PER_METER;
+
+ bool success = await JS.InvokeAsync("drawCachedImageOnCanvas",
+ CanvasRef,
+ MAP_CACHE_KEY,
+ mapCanvasX,
+ mapCanvasY - imageHeightCanvas,
+ imageWidthCanvas,
+ imageHeightCanvas);
+ }
+ catch
+ {
+ }
+
+ await ctx.RestoreAsync();
+ }
+
+ private async Task DrawLaserScannerPoints(Context2D ctx)
+ {
+ var scanData = GenerateLaserScanData();
+
+ double robotCanvasX = scanData.RobotX * BASE_PIXELS_PER_METER;
+ double robotCanvasY = scanData.RobotY * BASE_PIXELS_PER_METER;
+
+ await ctx.SaveAsync();
+
+ if (scanData.Points.Count > 0)
+ {
+ await ctx.BeginPathAsync();
+
+ for (int i = 0; i < scanData.Points.Count; i++)
+ {
+ var point = scanData.Points[i];
+ double pointCanvasX = point.X * BASE_PIXELS_PER_METER;
+ double pointCanvasY = point.Y * BASE_PIXELS_PER_METER;
+
+ if (i == 0)
+ {
+ await ctx.MoveToAsync(pointCanvasX, pointCanvasY);
+ }
+ else
+ {
+ await ctx.LineToAsync(pointCanvasX, pointCanvasY);
+ }
+ }
+
+ await ctx.StrokeStyleAsync("rgba(255, 100, 100, 0.8)");
+ await ctx.LineWidthAsync(2 / ZoomScale);
+ await ctx.StrokeAsync();
+
+ await ctx.LineToAsync(robotCanvasX, robotCanvasY);
+ await ctx.ClosePathAsync();
+ await ctx.FillStyleAsync("rgba(255, 100, 100, 0.1)");
+ await ctx.FillAsync(FillRule.NonZero);
+ }
+
+ await DrawRobotImage(ctx, robotCanvasX, robotCanvasY, scanData.RobotOrientation);
+ await DrawRobotOrientationArrows(ctx, robotCanvasX, robotCanvasY, scanData.RobotOrientation);
+
+ await ctx.RestoreAsync();
+ }
+
+ private async Task DrawRobotImage(Context2D ctx, double robotCanvasX, double robotCanvasY, double robotOrientation)
+ {
+ if (!RobotImageLoaded)
+ {
+ // Fallback to circle if image not loaded
+ await ctx.FillStyleAsync("rgba(0, 255, 0, 0.8)");
+ await ctx.BeginPathAsync();
+ await ctx.ArcAsync(robotCanvasX, robotCanvasY, 8 / ZoomScale, 0, Math.PI * 2);
+ await ctx.FillAsync(FillRule.NonZero);
+ return;
+ }
+
+ await ctx.SaveAsync();
+
+ double robotWidthPixels = RobotWidth * BASE_PIXELS_PER_METER;
+ double robotLengthPixels = RobotLength * BASE_PIXELS_PER_METER;
+
+ double scaledWidth = ZoomScale < 1 ? robotWidthPixels / ZoomScale : robotWidthPixels;
+ double scaledLength = ZoomScale < 1 ? robotLengthPixels / ZoomScale : robotLengthPixels;
+
+ await ctx.TranslateAsync(robotCanvasX, robotCanvasY);
+ await ctx.RotateAsync(robotOrientation);
+
+ try
+ {
+ bool success = await JS.InvokeAsync("drawImageOnCanvas",
+ CanvasRef,
+ "images/AMR-250.png",
+ -scaledLength / 2,
+ -scaledWidth / 2,
+ scaledLength,
+ scaledWidth);
+
+ if (!success)
+ {
+ await ctx.FillStyleAsync("rgba(0, 255, 0, 0.8)");
+ await ctx.FillRectAsync(-scaledLength / 2, -scaledWidth / 2, scaledLength, scaledWidth);
+ }
+ }
+ catch
+ {
+ await ctx.FillStyleAsync("rgba(0, 255, 0, 0.8)");
+ await ctx.FillRectAsync(-scaledLength / 2, -scaledWidth / 2, scaledLength, scaledWidth);
+ }
+
+ await ctx.RestoreAsync();
+ }
+
+ private async Task DrawRobotOrientationArrows(Context2D ctx, double robotCanvasX, double robotCanvasY, double robotOrientation)
+ {
+ double arrowLength = 30 / ZoomScale;
+ double arrowHeadSize = 10 / ZoomScale;
+
+ await ctx.StrokeStyleAsync("rgba(0, 100, 255, 1.0)");
+ await ctx.FillStyleAsync("rgba(0, 100, 255, 1.0)");
+ await ctx.LineWidthAsync(3 / ZoomScale);
+
+ await ctx.BeginPathAsync();
+ await ctx.MoveToAsync(robotCanvasX, robotCanvasY);
+ double xAxisEndX = robotCanvasX + Math.Cos(robotOrientation) * (arrowLength - arrowHeadSize + 1);
+ double xAxisEndY = robotCanvasY + Math.Sin(robotOrientation) * (arrowLength - arrowHeadSize + 1);
+ await ctx.LineToAsync(xAxisEndX, xAxisEndY);
+ await ctx.StrokeAsync();
+
+ await ctx.BeginPathAsync();
+ double xArrowTipX = robotCanvasX + Math.Cos(robotOrientation) * arrowLength;
+ double xArrowTipY = robotCanvasY + Math.Sin(robotOrientation) * arrowLength;
+ await ctx.MoveToAsync(xArrowTipX, xArrowTipY);
+ double xArrowAngle = robotOrientation + Math.PI;
+ await ctx.LineToAsync(xArrowTipX + Math.Cos(xArrowAngle + Math.PI / 6) * arrowHeadSize, xArrowTipY + Math.Sin(xArrowAngle + Math.PI / 6) * arrowHeadSize);
+ await ctx.LineToAsync(xArrowTipX + Math.Cos(xArrowAngle - Math.PI / 6) * arrowHeadSize, xArrowTipY + Math.Sin(xArrowAngle - Math.PI / 6) * arrowHeadSize);
+ await ctx.ClosePathAsync();
+ await ctx.FillAsync(FillRule.NonZero);
+
+ await ctx.StrokeStyleAsync("rgba(255, 50, 50, 1.0)");
+ await ctx.FillStyleAsync("rgba(255, 50, 50, 1.0)");
+ await ctx.LineWidthAsync(3 / ZoomScale);
+
+ double yAxisAngle = robotOrientation + Math.PI / 2;
+
+ await ctx.BeginPathAsync();
+ await ctx.MoveToAsync(robotCanvasX, robotCanvasY);
+ double yAxisEndX = robotCanvasX + Math.Cos(yAxisAngle) * (arrowLength - arrowHeadSize + 1);
+ double yAxisEndY = robotCanvasY + Math.Sin(yAxisAngle) * (arrowLength - arrowHeadSize + 1);
+ await ctx.LineToAsync(yAxisEndX, yAxisEndY);
+ await ctx.StrokeAsync();
+
+ await ctx.BeginPathAsync();
+ double yArrowTipX = robotCanvasX + Math.Cos(yAxisAngle) * arrowLength;
+ double yArrowTipY = robotCanvasY + Math.Sin(yAxisAngle) * arrowLength;
+ await ctx.MoveToAsync(yArrowTipX, yArrowTipY);
+ double yArrowAngle = yAxisAngle + Math.PI;
+ await ctx.LineToAsync(yArrowTipX + Math.Cos(yArrowAngle + Math.PI / 6) * arrowHeadSize, yArrowTipY + Math.Sin(yArrowAngle + Math.PI / 6) * arrowHeadSize);
+ await ctx.LineToAsync(yArrowTipX + Math.Cos(yArrowAngle - Math.PI / 6) * arrowHeadSize, yArrowTipY + Math.Sin(yArrowAngle - Math.PI / 6) * arrowHeadSize);
+ await ctx.ClosePathAsync();
+ await ctx.FillAsync(FillRule.NonZero);
+ }
+
private async Task DrawMouseIndicator(Context2D ctx)
{
await ctx.SaveAsync();
- await ctx.StrokeStyleAsync("rgba(255, 50, 50, 0.8)");
+ await ctx.StrokeStyleAsync("rgba(255, 50, 50, 0.8)");
await ctx.LineWidthAsync(1);
- await ctx.SetLineDashAsync(new double[] { 3, 3 });
+ await ctx.SetLineDashAsync(new double[] { 3, 3 });
await ctx.BeginPathAsync();
await ctx.MoveToAsync(MouseX, RulerHeight);
@@ -112,7 +367,7 @@
await ctx.StrokeAsync();
await ctx.BeginPathAsync();
- await ctx.MoveToAsync(RulerHeight, MouseY);
+ await ctx.MoveToAsync(RulerHeight, MouseY);
await ctx.LineToAsync(CanvasWidth, MouseY);
await ctx.StrokeAsync();
@@ -120,13 +375,13 @@
const double labelPadding = 7;
const double labelMargin = 8;
-
+
string coordinateText = $"({WorldMouseX:F2}m, {WorldMouseY:F2}m)";
await ctx.FontAsync("bold 12px Arial");
var textMetrics = await ctx.MeasureTextAsync(coordinateText);
double textWidth = textMetrics.Width;
- double textHeight = 16;
+ double textHeight = 16;
double labelX = MouseX + labelMargin;
double labelY = MouseY - textHeight - labelPadding * 2 - labelMargin;
@@ -140,10 +395,10 @@
labelY = MouseY + labelMargin;
}
- await ctx.FillStyleAsync("rgba(0, 0, 0, 0.8)");
+ await ctx.FillStyleAsync("rgba(0, 0, 0, 0.8)");
await ctx.FillRectAsync(labelX, labelY, textWidth + labelPadding * 2, textHeight + labelPadding * 2);
- await ctx.StrokeStyleAsync("rgba(255, 255, 255, 0.6)");
+ await ctx.StrokeStyleAsync("rgba(255,255,255,0.6)");
await ctx.LineWidthAsync(1);
await ctx.StrokeRectAsync(labelX, labelY, textWidth + labelPadding * 2, textHeight + labelPadding * 2);
@@ -159,16 +414,16 @@
await ctx.StrokeAsync();
await ctx.SaveAsync();
-
+
await ctx.TranslateAsync(labelX + labelPadding + textWidth / 2, labelY + textHeight / 2);
await ctx.ScaleAsync(1, -1);
-
+
await ctx.FillStyleAsync("white");
await ctx.FontAsync("bold 12px Arial");
await ctx.TextAlignAsync(TextAlign.Center);
await ctx.TextBaseLineAsync(TextBaseLine.Bottom);
await ctx.FillTextAsync(coordinateText, 0, 0);
-
+
await ctx.RestoreAsync();
}
@@ -326,23 +581,9 @@
await ctx.FillStyleAsync("red");
await ctx.BeginPathAsync();
- await ctx.ArcAsync(originCanvasX, originCanvasY, 10 / ZoomScale, 0, Math.PI * 2);
+ await ctx.ArcAsync(originCanvasX, originCanvasY, 8 / ZoomScale, 0, Math.PI * 2);
await ctx.FillAsync(FillRule.NonZero);
- await ctx.LineWidthAsync(2 / ZoomScale);
-
- await ctx.StrokeStyleAsync("blue");
- await ctx.BeginPathAsync();
- await ctx.MoveToAsync(-CanvasWidth / ZoomScale, originCanvasY);
- await ctx.LineToAsync(CanvasWidth / ZoomScale, originCanvasY);
- await ctx.StrokeAsync();
-
- await ctx.StrokeStyleAsync("red");
- await ctx.BeginPathAsync();
- await ctx.MoveToAsync(originCanvasX, -CanvasHeight / ZoomScale);
- await ctx.LineToAsync(originCanvasX, CanvasHeight / ZoomScale);
- await ctx.StrokeAsync();
-
double gridSpacingMeters = GetGridSpacingMeters();
double arrowLength = gridSpacingMeters * BASE_PIXELS_PER_METER;
double arrowHeadSize = 16 / ZoomScale;
@@ -353,13 +594,15 @@
await ctx.BeginPathAsync();
await ctx.MoveToAsync(originCanvasX, originCanvasY);
- await ctx.LineToAsync(originCanvasX + arrowLength, originCanvasY);
+ await ctx.LineToAsync(originCanvasX + arrowLength - arrowHeadSize, originCanvasY);
await ctx.StrokeAsync();
await ctx.BeginPathAsync();
- await ctx.MoveToAsync(originCanvasX + arrowLength, originCanvasY);
- await ctx.LineToAsync(originCanvasX + arrowLength - arrowHeadSize, originCanvasY - arrowHeadSize / 2);
- await ctx.LineToAsync(originCanvasX + arrowLength - arrowHeadSize, originCanvasY + arrowHeadSize / 2);
+ double xArrowTipX = originCanvasX + arrowLength;
+ double xArrowTipY = originCanvasY;
+ await ctx.MoveToAsync(xArrowTipX, xArrowTipY);
+ await ctx.LineToAsync(xArrowTipX - arrowHeadSize, xArrowTipY - arrowHeadSize / 2);
+ await ctx.LineToAsync(xArrowTipX - arrowHeadSize, xArrowTipY + arrowHeadSize / 2);
await ctx.ClosePathAsync();
await ctx.FillAsync(FillRule.NonZero);
@@ -369,13 +612,15 @@
await ctx.BeginPathAsync();
await ctx.MoveToAsync(originCanvasX, originCanvasY);
- await ctx.LineToAsync(originCanvasX, originCanvasY + arrowLength);
+ await ctx.LineToAsync(originCanvasX, originCanvasY + arrowLength - arrowHeadSize);
await ctx.StrokeAsync();
await ctx.BeginPathAsync();
- await ctx.MoveToAsync(originCanvasX, originCanvasY + arrowLength);
- await ctx.LineToAsync(originCanvasX - arrowHeadSize / 2, originCanvasY + arrowLength - arrowHeadSize);
- await ctx.LineToAsync(originCanvasX + arrowHeadSize / 2, originCanvasY + arrowLength - arrowHeadSize);
+ double yArrowTipX = originCanvasX;
+ double yArrowTipY = originCanvasY + arrowLength;
+ await ctx.MoveToAsync(yArrowTipX, yArrowTipY);
+ await ctx.LineToAsync(yArrowTipX - arrowHeadSize / 2, yArrowTipY - arrowHeadSize);
+ await ctx.LineToAsync(yArrowTipX + arrowHeadSize / 2, yArrowTipY - arrowHeadSize);
await ctx.ClosePathAsync();
await ctx.FillAsync(FillRule.NonZero);
}
@@ -515,9 +760,100 @@
await DrawCanvas();
}
+ private async Task ReloadMapImage()
+ {
+ MapImageLoaded = false;
+ await LoadMapImage();
+ await DrawCanvas();
+ StateHasChanged();
+ }
+
+ private LaserScanData GenerateLaserScanData()
+ {
+ // Robot position (in world coordinates)
+ double robotX = 2; // Robot at origin for demo
+ double robotY = 2;
+ double robotOrientation = 0; // Robot facing right (0 degrees)
+
+ Random random = new Random(42); // Fixed seed for consistent pattern
+
+ // Laser scanner parameters
+ const double maxRange = 8.0; // meters
+ const double minRange = 0.2; // meters (fix: was 7.0, should be minimum)
+ const int numPoints = 270; // Number of laser points
+ const double startAngle = -Math.PI / 2 - Math.PI / 4;
+ const double endAngle = Math.PI / 2 + Math.PI / 4;
+
+ double angleStep = (endAngle - startAngle) / (numPoints - 1);
+
+ var scanData = new LaserScanData
+ {
+ RobotX = robotX,
+ RobotY = robotY,
+ RobotOrientation = robotOrientation,
+ };
+
+ // Generate laser points
+ for (int i = 0; i < numPoints; i++)
+ {
+ double angle = startAngle + i * angleStep;
+
+ // Random range with some clustering around obstacles
+ double range;
+ if (random.NextDouble() < 0.3) // 30% chance of obstacles
+ {
+ range = random.NextDouble() * 3.0 + 1.0; // 1-4 meters (obstacles)
+ }
+ else if (random.NextDouble() < 0.1) // 10% chance of very close objects
+ {
+ range = random.NextDouble() * 0.8 + 0.2; // 0.2-1.0 meters
+ }
+ else
+ {
+ range = random.NextDouble() * maxRange * 0.7 + maxRange * 0.3; // Far points
+ }
+
+ // Add some noise to make it realistic
+ range += (random.NextDouble() - 0.5) * 0.1;
+ range = Math.Max(minRange, Math.Min(maxRange, range));
+
+ // Calculate point position relative to robot
+ double pointX = robotX + Math.Cos(angle + robotOrientation) * range;
+ double pointY = robotY + Math.Sin(angle + robotOrientation) * range;
+
+ scanData.Points.Add(new LaserScanPoint
+ {
+ X = pointX,
+ Y = pointY
+ });
+ }
+
+ return scanData;
+ }
+
+ public class LaserScanPoint
+ {
+ public double X { get; set; }
+ public double Y { get; set; }
+ }
+
+ public class LaserScanData
+ {
+ public double RobotX { get; set; }
+ public double RobotY { get; set; }
+ public double RobotOrientation { get; set; }
+ public List Points { get; set; } = new();
+ }
+
public class DomRect
{
public double Width { get; set; }
public double Height { get; set; }
}
+
+ public class ImageDimensions
+ {
+ public double Width { get; set; }
+ public double Height { get; set; }
+ }
}
diff --git a/RobotApp.Client/wwwroot/images/AMR-250.png b/RobotApp.Client/wwwroot/images/AMR-250.png
new file mode 100644
index 0000000..b4ae207
Binary files /dev/null and b/RobotApp.Client/wwwroot/images/AMR-250.png differ
diff --git a/RobotApp.Client/wwwroot/js/canvas.js b/RobotApp.Client/wwwroot/js/canvas.js
index 5b34e27..e956683 100644
--- a/RobotApp.Client/wwwroot/js/canvas.js
+++ b/RobotApp.Client/wwwroot/js/canvas.js
@@ -8,4 +8,94 @@
window.setCanvasSize = (canvas, width, height) => {
canvas.width = width;
canvas.height = height;
-}
\ No newline at end of file
+}
+
+// Image loading and caching functionality
+window.imageCache = new Map();
+
+window.preloadImage = (imagePath) => {
+ return new Promise((resolve, reject) => {
+ if (window.imageCache.has(imagePath)) {
+ resolve(window.imageCache.get(imagePath));
+ return;
+ }
+
+ const img = new Image();
+ img.onload = () => {
+ window.imageCache.set(imagePath, img);
+ resolve(img);
+ };
+ img.onerror = () => {
+ reject(new Error(`Failed to load image: ${imagePath}`));
+ };
+ img.src = imagePath;
+ });
+};
+
+window.preloadImageFromUrl = (url, cacheKey) => {
+ return new Promise((resolve, reject) => {
+ if (window.imageCache.has(cacheKey)) {
+ resolve(window.imageCache.get(cacheKey));
+ return;
+ }
+
+ const img = new Image();
+ img.onload = () => {
+ window.imageCache.set(cacheKey, img);
+ resolve(img);
+ };
+ img.onerror = (error) => {
+ reject(new Error(`Failed to load image from URL: ${url}`));
+ };
+
+ // Don't set crossOrigin for same-origin requests
+ // Only set it if you're loading from a different domain
+ // img.crossOrigin = 'anonymous';
+
+ img.src = url;
+ });
+};
+
+window.getImageDimensions = (cacheKey) => {
+ const img = window.imageCache.get(cacheKey);
+ if (!img) {
+ return { width: 0, height: 0 };
+ }
+
+ return {
+ width: img.naturalWidth || img.width,
+ height: img.naturalHeight || img.height
+ };
+};
+
+window.drawImageOnCanvas = (canvas, imagePath, x, y, width, height) => {
+ const ctx = canvas.getContext('2d');
+ const img = window.imageCache.get(imagePath);
+
+ if (!img) {
+ return false;
+ }
+
+ try {
+ ctx.drawImage(img, x, y, width, height);
+ return true;
+ } catch (error) {
+ return false;
+ }
+};
+
+window.drawCachedImageOnCanvas = (canvas, cacheKey, x, y, width, height) => {
+ const ctx = canvas.getContext('2d');
+ const img = window.imageCache.get(cacheKey);
+
+ if (!img) {
+ return false;
+ }
+
+ try {
+ ctx.drawImage(img, x, y, width, height);
+ return true;
+ } catch (error) {
+ return false;
+ }
+};
\ No newline at end of file
diff --git a/RobotApp/Controllers/ImagesController.cs b/RobotApp/Controllers/ImagesController.cs
index 05c2a21..b1995f6 100644
--- a/RobotApp/Controllers/ImagesController.cs
+++ b/RobotApp/Controllers/ImagesController.cs
@@ -1,11 +1,62 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
+using RobotApp.Services;
namespace RobotApp.Controllers;
[Route("api/[controller]")]
[ApiController]
[AllowAnonymous]
-public class ImagesController : ControllerBase
+public class ImagesController(Services.Logger Logger) : ControllerBase
{
+ [HttpGet]
+ [Route("map")]
+ public async Task GetMapImage()
+ {
+ try
+ {
+ await Task.Delay(1);
+
+ string fileName = "gara20250309.png";
+ string filePath = Path.Combine("maps", fileName);
+
+ if (System.IO.File.Exists(filePath))
+ {
+ byte[] imageBytes = await System.IO.File.ReadAllBytesAsync(filePath);
+ return File(imageBytes, "image/png");
+ }
+ else
+ {
+ string mapsDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "maps");
+ if (Directory.Exists(mapsDir))
+ {
+ var pngFiles = Directory.GetFiles(mapsDir, "*.png");
+
+ return NotFound(new
+ {
+ error = "Map image not found",
+ searchPath = filePath,
+ mapsDirectory = mapsDir,
+ availableFiles = pngFiles.Select(Path.GetFileName).ToArray(),
+ baseDirectory = AppDomain.CurrentDomain.BaseDirectory
+ });
+ }
+ else
+ {
+ Logger.Warning($"GetMapImage: Maps directory does not exist: {mapsDir}");
+ return NotFound(new
+ {
+ error = "Maps directory not found",
+ searchPath = mapsDir,
+ baseDirectory = AppDomain.CurrentDomain.BaseDirectory
+ });
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.Warning($"GetMapImage: Exception occurred - {ex.Message}");
+ return StatusCode(500, new { error = "Internal server error", message = ex.Message, stackTrace = ex.StackTrace });
+ }
+ }
}
diff --git a/RobotApp/Program.cs b/RobotApp/Program.cs
index e81d55c..fa67d52 100644
--- a/RobotApp/Program.cs
+++ b/RobotApp/Program.cs
@@ -22,6 +22,9 @@ builder.Services.AddScoped appDbOptions = options => options.UseSqlite(connectionString, b => b.MigrationsAssembly("RobotApp"));
@@ -68,6 +71,10 @@ app.UseAuthorization();
app.UseAntiforgery();
app.MapStaticAssets();
+
+// Map API Controllers
+app.MapControllers();
+
app.MapRazorComponents()
.AddInteractiveServerRenderMode()
.AddInteractiveWebAssemblyRenderMode()
diff --git a/RobotApp/Properties/launchSettings.json b/RobotApp/Properties/launchSettings.json
index b4aecb3..19731fb 100644
--- a/RobotApp/Properties/launchSettings.json
+++ b/RobotApp/Properties/launchSettings.json
@@ -5,6 +5,7 @@
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
+ "workingDirectory": "$(TargetDir)",
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "http://localhost:5229",
"environmentVariables": {
@@ -15,6 +16,7 @@
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
+ "workingDirectory": "$(TargetDir)",
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "https://localhost:7150;http://localhost:5229",
"environmentVariables": {
diff --git a/RobotApp/maps/gara20250309 .png b/RobotApp/maps/gara20250309 .png
new file mode 100644
index 0000000..bc4110d
Binary files /dev/null and b/RobotApp/maps/gara20250309 .png differ
diff --git a/RobotApp/robot.db b/RobotApp/robot.db
index 6249b2f..56f2f75 100644
Binary files a/RobotApp/robot.db and b/RobotApp/robot.db differ