Skip to content

Commit b5952ed

Browse files
committed
RE1-T115 POI Bug fixes
1 parent 3868dac commit b5952ed

9 files changed

Lines changed: 191 additions & 36 deletions

File tree

Lines changed: 89 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
33
using System.IO;
44
using System.Linq;
5+
using System.Net.Http;
56
using Resgrid.Model;
67
using Resgrid.Model.Providers;
78
using SharpKml.Dom;
@@ -15,38 +16,111 @@ public List<Coordinates> ImportFile(Stream input, bool isKmz)
1516
{
1617
var coordinates = new List<Coordinates>();
1718

19+
if (input == null)
20+
return coordinates;
21+
1822
try
1923
{
2024
KmlFile file;
2125
if (isKmz)
2226
{
2327
var kmz = KmzFile.Open(input);
24-
file = kmz.GetDefaultKmlFile();
28+
file = kmz?.GetDefaultKmlFile();
2529
}
2630
else
2731
file = KmlFile.Load(input);
2832

29-
30-
Kml kml = file.Root as Kml;
31-
if (kml != null)
33+
if (file?.Root is Kml kml)
3234
{
33-
foreach (var placemark in kml.Flatten().OfType<Placemark>())
34-
{
35-
var coords = new Coordinates();
36-
coords.Name = placemark.Name;
37-
coords.Latitude = placemark.CalculateBounds().Center.Latitude;
38-
coords.Longitude = placemark.CalculateBounds().Center.Longitude;
35+
ExtractCoordinates(kml, coordinates);
3936

40-
coordinates.Add(coords);
37+
// Resolve NetworkLinks to external KML/KMZ resources
38+
var networkLinks = kml.Flatten().OfType<NetworkLink>().ToList();
39+
foreach (var networkLink in networkLinks)
40+
{
41+
try
42+
{
43+
ResolveNetworkLink(networkLink, coordinates);
44+
}
45+
catch
46+
{
47+
// Skip failed network link resolution
48+
}
4149
}
4250
}
4351
}
44-
catch
52+
catch (Exception ex)
4553
{
46-
54+
System.Diagnostics.Debug.WriteLine($"KmlProvider.ImportFile error: {ex.Message}");
4755
}
48-
56+
4957
return coordinates;
5058
}
59+
60+
private static void ExtractCoordinates(Kml kml, List<Coordinates> coordinates)
61+
{
62+
foreach (var placemark in kml.Flatten().OfType<Placemark>())
63+
{
64+
var coords = new Coordinates();
65+
coords.Name = placemark.Name;
66+
67+
try
68+
{
69+
var bounds = placemark.CalculateBounds();
70+
if (bounds != null)
71+
{
72+
coords.Latitude = bounds.Center.Latitude;
73+
coords.Longitude = bounds.Center.Longitude;
74+
}
75+
}
76+
catch
77+
{
78+
// Skip placemarks that fail bounds calculation
79+
}
80+
81+
if (coords.Latitude.HasValue && coords.Longitude.HasValue)
82+
coordinates.Add(coords);
83+
}
84+
}
85+
86+
private static void ResolveNetworkLink(NetworkLink networkLink, List<Coordinates> coordinates)
87+
{
88+
if (networkLink?.Link?.Href == null)
89+
return;
90+
91+
var href = networkLink.Link.Href.OriginalString;
92+
if (string.IsNullOrWhiteSpace(href))
93+
return;
94+
95+
using (var httpClient = new HttpClient { Timeout = System.TimeSpan.FromSeconds(30) })
96+
{
97+
var response = httpClient.GetAsync(href).GetAwaiter().GetResult();
98+
if (!response.IsSuccessStatusCode)
99+
return;
100+
101+
using (var stream = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult())
102+
{
103+
bool isKmz = href.EndsWith(".kmz", StringComparison.OrdinalIgnoreCase);
104+
105+
KmlFile file;
106+
if (isKmz)
107+
{
108+
var kmz = KmzFile.Open(stream);
109+
file = kmz?.GetDefaultKmlFile();
110+
if (file == null)
111+
return;
112+
}
113+
else
114+
{
115+
file = KmlFile.Load(stream);
116+
}
117+
118+
if (file?.Root is Kml kml)
119+
{
120+
ExtractCoordinates(kml, coordinates);
121+
}
122+
}
123+
}
124+
}
51125
}
52126
}

Web/Resgrid.Web.Services/Controllers/v4/MappingController.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,8 @@ public async Task<ActionResult<GetMapDataResult>> GetMapDataAndMarkers()
469469
PoiTypeId = poiType.PoiTypeId,
470470
Name = poiType.Name,
471471
Color = poiType.Color,
472-
ImagePath = poiType.Image,
472+
ImagePath = null,
473+
PoiImage = poiType.Image,
473474
Marker = poiType.Marker,
474475
IsDestination = poiType.IsDestination
475476
});
@@ -630,7 +631,8 @@ public static PoiTypeResultData ConvertPoiTypeData(PoiType poiType)
630631
PoiTypeId = poiType.PoiTypeId,
631632
Name = poiType.Name,
632633
Color = poiType.Color,
633-
ImagePath = poiType.Image,
634+
ImagePath = null,
635+
PoiImage = poiType.Image,
634636
Marker = poiType.Marker,
635637
IsDestination = poiType.IsDestination
636638
};
@@ -649,7 +651,8 @@ public static PoiResultData ConvertPoiData(Poi poi, PoiType poiType)
649651
Latitude = poi.Latitude,
650652
Longitude = poi.Longitude,
651653
Color = poiType.Color,
652-
ImagePath = poiType.Image,
654+
ImagePath = null,
655+
PoiImage = poiType.Image,
653656
Marker = poiType.Marker,
654657
IsDestination = poiType.IsDestination
655658
};
@@ -664,7 +667,8 @@ private static MapMakerInfoData ConvertPoiMapMarker(Poi poi, PoiType poiType)
664667
Latitude = poi.Latitude,
665668
Title = GetPoiTitle(poi, poiType),
666669
InfoWindowContent = GetPoiInfoWindowContent(poi, poiType),
667-
ImagePath = poiType.Image,
670+
ImagePath = null,
671+
PoiImage = poiType.Image,
668672
Marker = poiType.Marker,
669673
Color = poiType.Color,
670674
Type = 4,

Web/Resgrid.Web.Services/Models/v4/Mapping/GetMapDataResult.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ public class MapMakerInfoData
5151
public string Note { get; set; }
5252
public string LayerId { get; set; }
5353
public string LayerName { get; set; }
54+
55+
/// <summary>
56+
/// The POI-specific custom icon image name (only set for POI markers, Type=4).
57+
/// New app versions should use this field instead of ImagePath for POI icons.
58+
/// ImagePath is set to null for POI markers so old apps fall back to their default icon.
59+
/// </summary>
60+
public string PoiImage { get; set; }
5461
}
5562

5663
public class PoiLayerData
@@ -61,5 +68,12 @@ public class PoiLayerData
6168
public string ImagePath { get; set; }
6269
public string Marker { get; set; }
6370
public bool IsDestination { get; set; }
71+
72+
/// <summary>
73+
/// The POI-specific custom icon image name.
74+
/// New app versions should use this field for POI type icons.
75+
/// ImagePath is set to null so old apps fall back to their default icon.
76+
/// </summary>
77+
public string PoiImage { get; set; }
6478
}
6579
}

Web/Resgrid.Web.Services/Models/v4/Mapping/PoiResultModels.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ public class PoiTypeResultData
1010
public string ImagePath { get; set; }
1111
public string Marker { get; set; }
1212
public bool IsDestination { get; set; }
13+
14+
/// <summary>
15+
/// The POI-specific custom icon image name.
16+
/// New app versions should use this field for POI type icons.
17+
/// ImagePath is set to null so old apps fall back to their default icon.
18+
/// </summary>
19+
public string PoiImage { get; set; }
1320
}
1421

1522
public class PoiResultData
@@ -26,6 +33,13 @@ public class PoiResultData
2633
public string ImagePath { get; set; }
2734
public string Marker { get; set; }
2835
public bool IsDestination { get; set; }
36+
37+
/// <summary>
38+
/// The POI-specific custom icon image name.
39+
/// New app versions should use this field for POI icons.
40+
/// ImagePath is set to null so old apps fall back to their default icon.
41+
/// </summary>
42+
public string PoiImage { get; set; }
2943
}
3044

3145
public class PoiTypesResult : StandardApiResponseV4Base

Web/Resgrid.Web.Services/Resgrid.Web.Services.xml

Lines changed: 28 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Web/Resgrid.Web/Areas/User/Controllers/MappingController.cs

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,8 @@ public async Task<IActionResult> POIs()
289289
{
290290
var modal = new POIsView();
291291
modal.Types = await _mappingService.GetPOITypesForDepartmentAsync(DepartmentId);
292+
modal.Message = TempData["ImportPOIsMessage"] as string;
293+
modal.ErrorMessage = TempData["ImportPOIsError"] as string;
292294

293295
return View(modal);
294296
}
@@ -305,37 +307,40 @@ public async Task<IActionResult> AddPOIType()
305307
}
306308

307309
[HttpGet]
308-
public async Task<IActionResult> ImportPOIs()
310+
public async Task<IActionResult> ImportPOIs(int poiTypeId)
309311
{
310312
var model = new ImportPOIsView();
313+
model.TypeId = poiTypeId;
311314

312315
return View(model);
313316
}
314317

315318
[HttpPost]
316319
public async Task<IActionResult> ImportPOIs(ImportPOIsView modal, IFormFile fileToUpload, CancellationToken cancellationToken)
317320
{
318-
if (fileToUpload != null && fileToUpload.Length > 0)
321+
if (fileToUpload == null || fileToUpload.Length == 0)
319322
{
320-
//Path.GetExtension(file.FileName).ToLower() == "kmz"
321-
322-
//var extenion = file.FileName.Substring(file.FileName.IndexOf(char.Parse(".")) + 1, file.FileName.Length - file.FileName.IndexOf(char.Parse(".")) - 1);
323-
var extenion = Path.GetExtension(fileToUpload.FileName).ToLower();
324-
325-
if (!String.IsNullOrWhiteSpace(extenion))
326-
extenion = extenion.ToLower();
323+
ModelState.AddModelError("fileToUpload", "Please select a file to upload.");
324+
}
325+
else
326+
{
327+
var extension = Path.GetExtension(fileToUpload.FileName).ToLower();
327328

328-
if (extenion != ".kml" && extenion != ".kmz")
329-
ModelState.AddModelError("fileToUpload", string.Format("File type ({0}) is not a KMZ or KML extension to import POIs.", extenion));
329+
if (extension != ".kml" && extension != ".kmz")
330+
ModelState.AddModelError("fileToUpload", string.Format("File type ({0}) is not a KMZ or KML extension to import POIs.", extension));
330331

331332
if (fileToUpload.Length > 10000000)
332333
ModelState.AddModelError("fileToUpload", "Document is too large, must be smaller then 10MB.");
333334
}
334335

336+
if (modal.TypeId <= 0)
337+
ModelState.AddModelError("TypeId", "Please select a POI type before importing.");
338+
335339
if (ModelState.IsValid)
336340
{
337341
var coordinates = _kmlProvider.ImportFile(fileToUpload.OpenReadStream(), Path.GetExtension(fileToUpload.FileName).ToLower() == ".kmz");
338342

343+
int importedCount = 0;
339344
foreach (var coordinate in coordinates)
340345
{
341346
var poi = new Poi();
@@ -348,9 +353,15 @@ public async Task<IActionResult> ImportPOIs(ImportPOIsView modal, IFormFile file
348353
poi.Longitude = coordinate.Longitude.Value;
349354

350355
await _mappingService.SavePOIAsync(poi, cancellationToken);
356+
importedCount++;
351357
}
352358
}
353359

360+
if (importedCount > 0)
361+
TempData["ImportPOIsMessage"] = string.Format("Successfully imported {0} POI(s).", importedCount);
362+
else
363+
TempData["ImportPOIsError"] = "No valid placemarks with coordinates could be found in the uploaded file.";
364+
354365
return RedirectToAction("POIs");
355366
}
356367

Web/Resgrid.Web/Areas/User/Models/Mapping/POIsView.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ namespace Resgrid.Web.Areas.User.Models.Mapping
66
public class POIsView
77
{
88
public List<PoiType> Types { get; set; }
9+
public string Message { get; set; }
10+
public string ErrorMessage { get; set; }
911

1012
public POIsView()
1113
{

Web/Resgrid.Web/Areas/User/Views/Mapping/ImportPOIs.cshtml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
<div class="wrapper wrapper-content">
3333
<div class="ibox float-e-margins">
3434
<div class="ibox-content">
35-
<form class="form-horizontal" role="form" asp-controller="Mapping" asp-action="ImportPOIs" asp-route-area="User" method="post">
35+
<form class="form-horizontal" role="form" asp-controller="Mapping" asp-action="ImportPOIs" asp-route-area="User" method="post" enctype="multipart/form-data">
3636

3737
<div class="row">
3838
<div class="col-md-8 col-md-offset-1">

Web/Resgrid.Web/Areas/User/Views/Mapping/POIs.cshtml

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,19 @@
3535
<div class="col-xs-12">
3636
<div class="wrapper wrapper-content">
3737
<div class="ibox float-e-margins">
38-
<div class="ibox-content">
39-
<div class="table-responsive">
40-
<table class="table table-striped">
41-
<thead>
42-
<tr>
38+
<div class="ibox-content">
39+
@if (!String.IsNullOrEmpty(Model.Message))
40+
{
41+
<div class="alert alert-success">@Model.Message</div>
42+
}
43+
@if (!String.IsNullOrEmpty(Model.ErrorMessage))
44+
{
45+
<div class="alert alert-danger">@Model.ErrorMessage</div>
46+
}
47+
<div class="table-responsive">
48+
<table class="table table-striped">
49+
<thead>
50+
<tr>
4351
<th>
4452
@localizer["Name"]
4553
</th>

0 commit comments

Comments
 (0)