Skip to content

Commit 8207b3f

Browse files
authored
Merge pull request #332 from Resgrid/develop
Develop
2 parents 11b789e + f70c267 commit 8207b3f

127 files changed

Lines changed: 6767 additions & 182089 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/settings.local.json

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
{
22
"permissions": {
33
"allow": [
4-
"mcp__dual-graph__graph_scan",
54
"mcp__dual-graph__graph_continue",
6-
"Bash(find G:ResgridResgridCoreResgrid.ModelBilling -type f -name *.cs)",
7-
"Bash(python3:*)",
5+
"mcp__dual-graph__graph_read",
6+
"Bash(dotnet build:*)",
7+
"mcp__dual-graph__graph_register_edit",
8+
"mcp__dual-graph__graph_scan",
9+
"Bash(find /g/Resgrid/Resgrid -type d \\\\\\(-name *mobile* -o -name *Mobile* -o -name *app* -o -name *App* -o -name *apps* -o -name *Apps* \\\\\\))",
810
"Bash(xargs grep:*)",
9-
"Bash(grep -E \"\\\\.cs$|Program\")",
10-
"Bash(grep -l \"DepartmentMembers\\\\|DepartmentMember\" \"G:\\\\Resgrid\\\\Resgrid/Repositories/Resgrid.Repositories.DataRepository/\"*.cs)",
11-
"Bash(find /g/Resgrid/Resgrid/Repositories/Resgrid.Repositories.DataRepository/Servers -name *.cs)",
12-
"Bash(find G:/Resgrid/Resgrid -newer G:/Resgrid/Resgrid/CLAUDE.md -name *.cs -not -path */obj/* -not -path */.git/*)",
13-
"Bash(dotnet build:*)"
11+
"Bash(grep -r \"mapbox\\\\|react-native\\\\|rnmapbox\" /g/Resgrid/Resgrid --include=package.json --include=*.ts --include=*.tsx)",
12+
"Bash(find G:ResgridResgridWebResgrid.WebAreasUserViews -type f -name *.cshtml)",
13+
"Bash(grep -r \"google.maps\\\\|mapboxgl\\\\|leaflet\\\\|openstreetmap\" G:/Resgrid/Resgrid/Web/Resgrid.Web/Areas/User/Apps/src --include=*.ts)",
14+
"Bash(grep -r \"leaflet\\\\|L\\\\.tileLayer\\\\|OpenStreetMap\" /g/Resgrid/Resgrid/Web/Resgrid.Web.Services --include=*.cs)",
15+
"Bash(find /g/Resgrid/Resgrid -type f -name *.swift -o -name *.kt -o -name *.java)"
1416
]
1517
},
1618
"hooks": {

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,3 +274,4 @@ Web/Resgrid.WebCore/wwwroot/lib/*
274274
.dual-graph/
275275
.claude/settings.local.json
276276
.claude/settings.local.json
277+
/Web/Resgrid.Web/wwwroot/js/ng/chunks

Core/Resgrid.Config/MappingConfig.cs

Lines changed: 257 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1-
namespace Resgrid.Config
1+
using System;
2+
3+
namespace Resgrid.Config
24
{
35
public static class MappingConfig
46
{
7+
public const string LeafletMapProvider = "leaflet";
8+
public const string MapboxMapProvider = "mapbox";
9+
510
public static int PersonnelLocationStaleSeconds = 30;
611
public static int UnitLocationStaleSeconds = 30;
712
public static int PersonnelLocationMinMeters = 20;
@@ -53,64 +58,292 @@ public static class MappingConfig
5358
public static string BigBoardOSMKey = "";
5459

5560
public static string DispatchAppMapboxKey = "";
61+
public static string WebsiteMapboxKey = "";
62+
public static string WebsiteMapboxAccessToken = "";
63+
public static string WebsiteMapMode = LeafletMapProvider;
5664

5765
public static string LeafletTileUrl = "https://api.maptiler.com/maps/streets/{{z}}/{{x}}/{{y}}.png?key={0}";
5866
public static string MapBoxTileUrl = "";
67+
public static string MapBoxStyleUrl = "";
5968

6069
public static string LeafletAttribution = "© OpenStreetMap contributors CC-BY-SA";
70+
public static string MapBoxAttribution = "© Mapbox © OpenStreetMap contributors";
6171

6272
/***********************************
6373
* Geocoding and Routing Service URLs
6474
***********************************/
6575
public static string NominatimUrl = "https://nominatim.openstreetmap.org";
6676
public static string OsrmUrl = "https://router.project-osrm.org";
6777

78+
public static ResolvedMapConfig GetMapConfig(string key)
79+
{
80+
var surfaceKey = string.IsNullOrWhiteSpace(key) ? InfoConfig.WebsiteKey : key;
81+
var mapProvider = GetPreferredMapProvider(surfaceKey);
82+
83+
if (mapProvider == MapboxMapProvider)
84+
{
85+
var mapboxAccessToken = NormalizePublicMapboxAccessToken(GetSystemMapboxAccessToken(surfaceKey));
86+
87+
if (TryCreateMapboxConfig(MapBoxStyleUrl, mapboxAccessToken, false, out var mapboxConfig))
88+
return mapboxConfig;
89+
90+
if (!string.IsNullOrWhiteSpace(mapboxAccessToken) && !string.IsNullOrWhiteSpace(MapBoxTileUrl))
91+
{
92+
return new ResolvedMapConfig
93+
{
94+
MapProvider = MapboxMapProvider,
95+
TileUrl = ReplaceTileKey(MapBoxTileUrl, mapboxAccessToken),
96+
StyleUrl = MapBoxStyleUrl,
97+
AccessToken = mapboxAccessToken,
98+
Attribution = MapBoxAttribution,
99+
IsDepartmentOverride = false
100+
};
101+
}
102+
}
103+
104+
return new ResolvedMapConfig
105+
{
106+
MapProvider = LeafletMapProvider,
107+
TileUrl = GetLegacyLeafletUrl(surfaceKey),
108+
StyleUrl = string.Empty,
109+
AccessToken = string.Empty,
110+
Attribution = LeafletAttribution,
111+
IsDepartmentOverride = false
112+
};
113+
}
114+
115+
public static bool TryCreateMapboxConfig(string styleUrl, string accessToken, bool isDepartmentOverride, out ResolvedMapConfig mapConfig)
116+
{
117+
mapConfig = null;
118+
var publicAccessToken = NormalizePublicMapboxAccessToken(accessToken);
119+
120+
if (string.IsNullOrWhiteSpace(styleUrl) || string.IsNullOrWhiteSpace(publicAccessToken))
121+
return false;
122+
123+
var styleId = GetMapboxStyleId(styleUrl);
124+
125+
if (string.IsNullOrWhiteSpace(styleId))
126+
return false;
127+
128+
mapConfig = new ResolvedMapConfig
129+
{
130+
MapProvider = MapboxMapProvider,
131+
TileUrl = $"https://api.mapbox.com/styles/v1/{styleId}/tiles/256/{{z}}/{{x}}/{{y}}@2x?access_token={publicAccessToken}",
132+
StyleUrl = NormalizeMapboxStyleUrl(styleUrl, styleId),
133+
AccessToken = publicAccessToken,
134+
Attribution = MapBoxAttribution,
135+
IsDepartmentOverride = isDepartmentOverride
136+
};
137+
138+
return true;
139+
}
140+
141+
public static bool IsSupportedMapboxStyleUrl(string styleUrl)
142+
{
143+
return !string.IsNullOrWhiteSpace(GetMapboxStyleId(styleUrl));
144+
}
145+
68146
public static string GetWebsiteOSMUrl()
69147
{
70-
if (!string.IsNullOrWhiteSpace(WebsiteOSMKey))
71-
return string.Format(MapBoxTileUrl, WebsiteOSMKey);
72-
else
73-
return LeafletTileUrl;
148+
return GetMapConfig(InfoConfig.WebsiteKey).TileUrl;
74149
}
75150

76151
public static string GetApiOSMUrl()
77152
{
78-
if (!string.IsNullOrWhiteSpace(ApiOSMKey))
79-
return string.Format(LeafletTileUrl, ApiOSMKey);
80-
else
81-
return LeafletTileUrl;
153+
return GetMapConfig(InfoConfig.ApiKey).TileUrl;
82154
}
83155

84156
public static string GetResponderAppOSMUrl()
85157
{
86-
if (!string.IsNullOrWhiteSpace(ResponderAppOSMKey))
87-
return string.Format(LeafletTileUrl, ResponderAppOSMKey);
88-
else
89-
return LeafletTileUrl;
158+
return GetMapConfig(InfoConfig.ResponderAppKey).TileUrl;
90159
}
91160

92161
public static string GetUnitAppOSMUrl()
93162
{
94-
if (!string.IsNullOrWhiteSpace(UnitAppOSMKey))
95-
return string.Format(LeafletTileUrl, UnitAppOSMKey);
96-
else
97-
return LeafletTileUrl;
163+
return GetMapConfig(InfoConfig.UnitAppKey).TileUrl;
98164
}
99165

100166
public static string GetBigBoardAppOSMUrl()
101167
{
102-
if (!string.IsNullOrWhiteSpace(BigBoardOSMKey))
103-
return string.Format(LeafletTileUrl, BigBoardOSMKey);
104-
else
105-
return LeafletTileUrl;
168+
return GetMapConfig(InfoConfig.BigBoardKey).TileUrl;
106169
}
107170

108171
public static string GetDispatchAppOSMUrl()
109172
{
110-
if (!string.IsNullOrWhiteSpace(DispatchAppMapboxKey))
111-
return string.Format(MapBoxTileUrl, DispatchAppMapboxKey);
112-
else
173+
return GetMapConfig(InfoConfig.DispatchAppKey).TileUrl;
174+
}
175+
176+
private static string GetLegacyLeafletUrl(string key)
177+
{
178+
if (key == InfoConfig.WebsiteKey)
179+
{
180+
if (!string.IsNullOrWhiteSpace(WebsiteOSMKey))
181+
return ReplaceTileKey(LeafletTileUrl, WebsiteOSMKey);
182+
183+
return LeafletTileUrl;
184+
}
185+
186+
if (key == InfoConfig.ApiKey)
187+
{
188+
if (!string.IsNullOrWhiteSpace(ApiOSMKey))
189+
return ReplaceTileKey(LeafletTileUrl, ApiOSMKey);
190+
191+
return LeafletTileUrl;
192+
}
193+
194+
if (key == InfoConfig.ResponderAppKey)
195+
{
196+
if (!string.IsNullOrWhiteSpace(ResponderAppOSMKey))
197+
return ReplaceTileKey(LeafletTileUrl, ResponderAppOSMKey);
198+
113199
return LeafletTileUrl;
200+
}
201+
202+
if (key == InfoConfig.UnitAppKey)
203+
{
204+
if (!string.IsNullOrWhiteSpace(UnitAppOSMKey))
205+
return ReplaceTileKey(LeafletTileUrl, UnitAppOSMKey);
206+
207+
return LeafletTileUrl;
208+
}
209+
210+
if (key == InfoConfig.BigBoardKey)
211+
{
212+
if (!string.IsNullOrWhiteSpace(BigBoardOSMKey))
213+
return ReplaceTileKey(LeafletTileUrl, BigBoardOSMKey);
214+
215+
return LeafletTileUrl;
216+
}
217+
218+
if (key == InfoConfig.DispatchAppKey)
219+
{
220+
if (!string.IsNullOrWhiteSpace(DispatchAppOSMKey))
221+
return ReplaceTileKey(LeafletTileUrl, DispatchAppOSMKey);
222+
223+
return LeafletTileUrl;
224+
}
225+
226+
return LeafletTileUrl;
227+
}
228+
229+
private static string GetPreferredMapProvider(string key)
230+
{
231+
if (key == InfoConfig.WebsiteKey)
232+
return NormalizeMapProvider(WebsiteMapMode);
233+
234+
if (key == InfoConfig.DispatchAppKey && !string.IsNullOrWhiteSpace(DispatchAppMapboxKey))
235+
return MapboxMapProvider;
236+
237+
if (key == InfoConfig.UnitAppKey && !string.IsNullOrWhiteSpace(UnitAppMapBoxKey))
238+
return MapboxMapProvider;
239+
240+
return LeafletMapProvider;
241+
}
242+
243+
private static string GetSystemMapboxAccessToken(string key)
244+
{
245+
if (key == InfoConfig.WebsiteKey)
246+
{
247+
if (!string.IsNullOrWhiteSpace(WebsiteMapboxAccessToken))
248+
return WebsiteMapboxAccessToken;
249+
250+
if (!string.IsNullOrWhiteSpace(WebsiteMapboxKey))
251+
return WebsiteMapboxKey;
252+
253+
return WebsiteOSMKey;
254+
}
255+
256+
if (key == InfoConfig.DispatchAppKey)
257+
return DispatchAppMapboxKey;
258+
259+
if (key == InfoConfig.UnitAppKey)
260+
return UnitAppMapBoxKey;
261+
262+
return string.Empty;
263+
}
264+
265+
private static string NormalizeMapProvider(string provider)
266+
{
267+
if (string.IsNullOrWhiteSpace(provider))
268+
return LeafletMapProvider;
269+
270+
return provider.Trim().Equals(MapboxMapProvider, StringComparison.InvariantCultureIgnoreCase)
271+
? MapboxMapProvider
272+
: LeafletMapProvider;
273+
}
274+
275+
private static string ReplaceTileKey(string tileUrl, string key)
276+
{
277+
if (string.IsNullOrWhiteSpace(tileUrl))
278+
return tileUrl;
279+
280+
var normalizedTileUrl = tileUrl
281+
.Replace("{{", "{", StringComparison.InvariantCulture)
282+
.Replace("}}", "}", StringComparison.InvariantCulture);
283+
284+
if (string.IsNullOrWhiteSpace(key))
285+
return normalizedTileUrl;
286+
287+
return normalizedTileUrl.Replace("{0}", key, StringComparison.InvariantCulture);
288+
}
289+
290+
private static string NormalizeMapboxStyleUrl(string styleUrl, string styleId)
291+
{
292+
if (styleUrl.StartsWith("mapbox://styles/", StringComparison.InvariantCultureIgnoreCase))
293+
return styleUrl;
294+
295+
return $"mapbox://styles/{styleId}";
296+
}
297+
298+
private static string NormalizePublicMapboxAccessToken(string accessToken)
299+
{
300+
if (string.IsNullOrWhiteSpace(accessToken))
301+
return string.Empty;
302+
303+
var trimmedAccessToken = accessToken.Trim();
304+
305+
return trimmedAccessToken.StartsWith("pk.", StringComparison.Ordinal)
306+
? trimmedAccessToken
307+
: string.Empty;
308+
}
309+
310+
private static string GetMapboxStyleId(string styleUrl)
311+
{
312+
if (string.IsNullOrWhiteSpace(styleUrl))
313+
return null;
314+
315+
var trimmedStyleUrl = styleUrl.Trim();
316+
317+
if (trimmedStyleUrl.StartsWith("mapbox://styles/", StringComparison.InvariantCultureIgnoreCase))
318+
return ExtractMapboxStyleId(trimmedStyleUrl.Substring("mapbox://styles/".Length));
319+
320+
if (Uri.TryCreate(trimmedStyleUrl, UriKind.Absolute, out var mapboxStyleUri))
321+
{
322+
var path = mapboxStyleUri.AbsolutePath.Trim('/');
323+
var stylesIndex = path.IndexOf("styles/v1/", StringComparison.InvariantCultureIgnoreCase);
324+
325+
if (stylesIndex >= 0)
326+
{
327+
var stylePath = path.Substring(stylesIndex + "styles/v1/".Length);
328+
return ExtractMapboxStyleId(stylePath);
329+
}
330+
}
331+
332+
return null;
333+
}
334+
335+
private static string ExtractMapboxStyleId(string stylePath)
336+
{
337+
if (string.IsNullOrWhiteSpace(stylePath))
338+
return null;
339+
340+
var normalizedPath = stylePath.Trim('/');
341+
var pathSegments = normalizedPath.Split('/', StringSplitOptions.RemoveEmptyEntries);
342+
343+
if (pathSegments.Length < 2)
344+
return null;
345+
346+
return $"{pathSegments[0]}/{pathSegments[1]}";
114347
}
115348
}
116349
}

Core/Resgrid.Config/NumberProviderConfig.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public static class NumberProviderConfig
1515
public static string TwilioResgridNumber = "";
1616
public static string TwilioApiUrl = SystemBehaviorConfig.ResgridApiBaseUrl + "/api/Twilio/IncomingMessage";
1717
public static string TwilioVoiceCallApiUrl = SystemBehaviorConfig.ResgridApiBaseUrl + "/api/Twilio/VoiceCall?userId={0}&callId={1}";
18+
public static string TwilioVoiceVerificationApiUrl = SystemBehaviorConfig.ResgridApiBaseUrl + "/api/Twilio/VoiceVerification?userId={0}&contactType={1}";
1819
public static string TwilioVoiceApiUrl = SystemBehaviorConfig.ResgridApiBaseUrl + "/api/Twilio/InboundVoice";
1920

2021
// Diafaan (https://www.diafaan.com)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace Resgrid.Config
2+
{
3+
public class ResolvedMapConfig
4+
{
5+
public string MapProvider { get; set; }
6+
7+
public string TileUrl { get; set; }
8+
9+
public string StyleUrl { get; set; }
10+
11+
public string AccessToken { get; set; }
12+
13+
public string Attribution { get; set; }
14+
15+
public bool IsDepartmentOverride { get; set; }
16+
}
17+
}

0 commit comments

Comments
 (0)