Skip to content

Commit fc0d880

Browse files
authored
Merge pull request #413 from dymaptic/feature/410-wmts-layer
WMTS Layer
2 parents 9fbeaef + b728771 commit fc0d880

14 files changed

Lines changed: 187 additions & 158 deletions

File tree

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
<PropertyGroup>
33
<Nullable>enable</Nullable>
44
<ImplicitUsings>enable</ImplicitUsings>
5-
<CoreVersion>4.0.0.3</CoreVersion>
5+
<CoreVersion>4.0.0.5</CoreVersion>
66
</PropertyGroup>
77
</Project>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
@page "/wmtslayers"
2+
3+
4+
<h3>WMTS Layers</h3>
5+
6+
<div class="links-div">
7+
<a class="btn btn-secondary" target="_blank" href="https://developers.arcgis.com/javascript/latest/sample-code/layers-wmts/">ArcGIS API for JavaScript</a>
8+
<a class="btn btn-primary" target="_blank" href="https://www.ign.es/web/ign/portal">Instituto Geográfico Nacional</a>
9+
</div>
10+
<p class="instructions">
11+
This sample shows how to add an instance of WMTSLayer. The WMTSLayer is used to create layers based on OGC Web Map Tile Service (WMTS). Typically, a WMTS service acts as a directory of WMTS layers, you can set a specific layer using the activeLayer property, (or else it will default to the first sublayer). The WMTSLayer initially executes a WMTS GetCapabilities request, which might require CORS or a proxy page.
12+
</p>
13+
<p class="instructions">
14+
The layers used in this sample are from <a target="_blank" href="https://www.ign.es/">Instituto Geográfico Nacional</a>. Use the Basemap Toggle component to toggle between the layers.
15+
</p>
16+
17+
<Basemap @ref="_orthoIGNBasemap"
18+
ThumbnailUrl="https://www.ign.es/wmts/mapa-raster?SERVICE=WMTS&VERSION=1.0.0&REQUEST=GetTile&LAYER=MTN&STYLE=default&FORMAT=image%2Fjpeg&TILEMATRIXSET=GoogleMapsCompatible&TILEMATRIX=6&TILEROW=24&TILECOL=31">
19+
<WMTSLayer Url="https://www.ign.es/wmts/mapa-raster"
20+
ActiveLayer="@(new WMTSSublayer(wMTSSublayerId: "MTN", tileMatrixSetId: "GoogleMapsCompatible"))"
21+
ServiceMode="ServiceMode.KVP"
22+
Copyright="@("<a href=\"https://www.ign.es/\" target=\"_blank\">Instituto Geográfico Nacional</a>")"/>
23+
</Basemap>
24+
25+
<MapView class="map-view" Scale="4622333" Longitude="-3.7038" Latitude="40.4168">
26+
<Map>
27+
<Basemap @ref="_plainIGNBasemap"
28+
ThumbnailUrl="https://www.ign.es/wmts/ign-base?SERVICE=WMTS&VERSION=1.0.0&REQUEST=GetTile&LAYER=IGNBase-gris&STYLE=default&FORMAT=image%2Fjpeg&TILEMATRIXSET=GoogleMapsCompatible&TILEMATRIX=6&TILEROW=24&TILECOL=31">
29+
<WMTSLayer Url="https://www.ign.es/wmts/ign-base"
30+
ActiveLayer="@(new WMTSSublayer(wMTSSublayerId: "IGNBase-gris", tileMatrixSetId: "GoogleMapsCompatible"))"
31+
ServiceMode="ServiceMode.KVP"
32+
Copyright="@("<a href=\"https://www.ign.es/\" target=\"_blank\">Instituto Geográfico Nacional</a>")"/>
33+
</Basemap>
34+
</Map>
35+
<BasemapToggleWidget Position="OverlayPosition.BottomLeft" NextBasemap="_orthoIGNBasemap" />
36+
</MapView>
37+
38+
@code {
39+
private Basemap? _plainIGNBasemap;
40+
private Basemap? _orthoIGNBasemap;
41+
}

samples/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@
122122
new("wcslayers", "WCS Layers", "oi-project"),
123123
new("wfslayers", "WFS Layers", null, "wfs.svg"),
124124
new("wmslayers", "WMS Layers", null, "wms.svg"),
125+
new("wmtslayers", "WMTS Layers", null, "wmts.svg"),
125126
new("bingmaps-layer", "Bing Maps Layer", null, "bing.png"),
126127
new("imagerylayer", "Imagery Layers", "oi-image"),
127128
new("imagery-tile-layer", "Imagery Tile Layers", null, "tile.png"),

samples/dymaptic.GeoBlazor.Core.Sample.Shared/wwwroot/css/site.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1160,6 +1160,10 @@ input[type=radio] {
11601160
padding: 1rem;
11611161
}
11621162

1163+
.esri-view .esri-view-surface:focus:after {
1164+
z-index: 0;
1165+
}
1166+
11631167
@media (max-width: 49.9rem) {
11641168
input[type="text"], input[type="number"] {
11651169
width: 5.5rem;
Lines changed: 10 additions & 0 deletions
Loading

src/dymaptic.GeoBlazor.Core/Components/Basemap.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,12 @@ public override async Task UnregisterChildComponent(MapComponent child)
6868
switch (child)
6969
{
7070
case Layer layer:
71-
await View!.RemoveLayer(layer, layer.IsBasemapReferenceLayer != true,
72-
layer.IsBasemapReferenceLayer == true);
71+
// Basemap might be declared outside the view for BasemapToggleWidget
72+
if (View is not null)
73+
{
74+
await View!.RemoveLayer(layer, layer.IsBasemapReferenceLayer != true,
75+
layer.IsBasemapReferenceLayer == true);
76+
}
7377

7478
break;
7579
default:

src/dymaptic.GeoBlazor.Core/Components/Layers/Layer.cs

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,12 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
488488
/// </summary>
489489
public async Task UpdateLayer()
490490
{
491+
if (JsComponentReference is null || CoreJsModule is null || !MapRendered || IsDisposed)
492+
{
493+
// don't update until the component has been returned from JavaScript
494+
return;
495+
}
496+
491497
if (MapRendered && !_delayedUpdate)
492498
{
493499
// for components added after the map has rendered, wait one render cycle to get all children before updating
@@ -498,26 +504,6 @@ public async Task UpdateLayer()
498504
}
499505

500506
_delayedUpdate = false;
501-
502-
if (CoreJsModule is null)
503-
{
504-
return;
505-
}
506-
507-
try
508-
{
509-
JsComponentReference ??= await CoreJsModule!.InvokeAsync<IJSObjectReference?>(
510-
"getJsComponent", CancellationTokenSource.Token, Id);
511-
}
512-
catch
513-
{
514-
// this is expected if the component is not yet built
515-
}
516-
517-
if (JsComponentReference is null || !MapRendered || IsDisposed)
518-
{
519-
return;
520-
}
521507

522508
// ReSharper disable once RedundantCast
523509
await JsComponentReference!.InvokeAsync<string?>("updateComponent", CancellationTokenSource.Token, (object)this);

src/dymaptic.GeoBlazor.Core/Components/Layers/WMTSLayer.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
namespace dymaptic.GeoBlazor.Core.Components.Layers;
22

3-
[Experimental("GeoBlazor_Untested", UrlFormat = "https://docs.geoblazor.com/pages/untested.html")]
43
public partial class WMTSLayer
54
{
65
// Add custom code to this file to override generated code

src/dymaptic.GeoBlazor.Core/Components/Views/MapView.razor.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1882,8 +1882,15 @@ await CoreJsModule.InvokeVoidAsync("setSpatialReference",
18821882
{
18831883
if (!Widgets.Any(w => w is PopupWidget) && CoreJsModule is not null)
18841884
{
1885-
var popupWidget = new PopupWidget();
1886-
AddWidget(popupWidget);
1885+
// add as custom logic since this is before `MapRendered` and calling `AddWidget` will exit early
1886+
var popupWidget = new PopupWidget
1887+
{
1888+
Parent = this,
1889+
View = this,
1890+
CoreJsModule = CoreJsModule
1891+
};
1892+
_newWidgets.Add(popupWidget);
1893+
StateHasChanged();
18871894
}
18881895

18891896
return Task.FromResult(Widgets.FirstOrDefault(w => w is PopupWidget) as PopupWidget);
@@ -2399,11 +2406,12 @@ await CoreJsModule.InvokeVoidAsync("buildMapView", CancellationTokenSource.Token
23992406
DotNetComponentReference, Longitude, Latitude, Rotation, Map, Zoom, Scale,
24002407
mapType, Widgets, Graphics, SpatialReference, Constraints, Extent, BackgroundColor,
24012408
EventRateLimitInMilliseconds, GetActiveEventHandlers(), IsServer, HighlightOptions, PopupEnabled);
2409+
2410+
// must be after main render, but before the boolean flags are set
2411+
await GetPopupWidget();
24022412

24032413
Rendering = false;
24042414
MapRendered = true;
2405-
// must be after `Rendering = false`
2406-
await GetPopupWidget();
24072415
});
24082416
}
24092417
#endregion

src/dymaptic.GeoBlazor.Core/Enums/ServiceMode.cs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,30 @@ namespace dymaptic.GeoBlazor.Core.Enums;
88
/// default "RESTful"
99
/// <a target="_blank" href="https://developers.arcgis.com/javascript/latest/api-reference/esri-layers-WMTSLayer.html#serviceMode">ArcGIS Maps SDK for JavaScript</a>
1010
/// </summary>
11-
[JsonConverter(typeof(EnumToKebabCaseStringConverter<ServiceMode>))]
11+
[JsonConverter(typeof(ServiceModeConverter))]
1212
public enum ServiceMode
1313
{
1414
#pragma warning disable CS1591
1515
RESTful,
1616
KVP
1717
#pragma warning restore CS1591
1818
}
19+
20+
// custom converter, this type keeps exact casing from enum
21+
internal class ServiceModeConverter : JsonConverter<ServiceMode>
22+
{
23+
public override ServiceMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
24+
{
25+
return reader.GetString() switch
26+
{
27+
"RESTful" => ServiceMode.RESTful,
28+
"KVP" => ServiceMode.KVP,
29+
_ => throw new JsonException($"Unknown service mode: {reader.GetString()}")
30+
};
31+
}
32+
33+
public override void Write(Utf8JsonWriter writer, ServiceMode value, JsonSerializerOptions options)
34+
{
35+
writer.WriteStringValue(value.ToString());
36+
}
37+
}

0 commit comments

Comments
 (0)