-
Notifications
You must be signed in to change notification settings - Fork 0
/
app.py
253 lines (230 loc) · 11.6 KB
/
app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
import folium
from folium.plugins import Draw
import lxml
from owslib.wms import WebMapService
from requests import RequestException
import viktor as vkt
WMS_DEFAULT = "https://service.pdok.nl/wandelnet/regionale-wandelnetwerken/wms/v1_0?version=1.3.0&request=getcapabilities&service=wms"
def _get_layer_options(params, **kwargs) -> list:
"""Get layer options from connected WMS-layer"""
layers = []
if params.wms_details.wms_input:
try:
wms = WebMapService(params.wms_details.wms_input, params.wms_details.wms_version)
layers = list(wms.contents)
except (RequestException, lxml.etree.XMLSyntaxError):
pass
if not layers:
layers = ["Please enter WMS url and version"]
return layers
def _validate_wms_details(params, **kwargs) -> None:
"""Validates the WMS input, before the user is allowed to go to the next step."""
try:
WebMapService(params.wms_details.wms_input, params.wms_details.wms_version)
except (RequestException, lxml.etree.XMLSyntaxError):
raise vkt.UserError("Please enter a valid WMS-url first. Click on the button 'Use sample WMS' for an example.")
def connect_to_WMS(wms_url: str, wms_version: str) -> WebMapService:
"""Connects to the WMS-layers"""
try:
wms = WebMapService(wms_url, version=wms_version)
except RequestException:
raise vkt.UserError("The provided url seems to be incorrect. Please check input for WMS url.")
except lxml.etree.XMLSyntaxError:
raise vkt.UserError("The provided url does not seem to point at a WMS-layer, please check input for WMS url.")
return wms
def get_WMS_details(wms: WebMapService) -> dict:
"""Gets the details from the WMS layer, such as the base url, layers and name."""
base_url = wms.url
layers = list(wms.contents)
name = wms.identification.title
return {"Base url": base_url, "Layers": layers, "Name": name}
class Parametrization(vkt.ViktorParametrization):
"""Parametrization for the sample Leaflet app."""
introduction = vkt.Step("Introduction", views=["leaflet_introduction"])
introduction.welcome_text = vkt.Text(
"# WMS in VIKTOR \n"
"Welcome to the WMS app. In this app is shown how WMS layers are added to a VIKTOR app. This is done using the "
"Python package Folium and Leaflet."
" \n"
"## What is WMS? \n"
"A WMS (Web Map Service) layer is a type of GIS data layer that allows users to request and "
"receive map images over the internet. \n"
"The main advantage of a WMS-layer is that it ensures the data is always up-to-date, since the "
"source of the data is located in one place. Additionally, since WMS is hosted, it is not neceassary to host "
"it yourself. \n"
" \n"
" ## Leaflet \n"
"WMS layers can be added using the powerful open-source JavaScript library [Leaflet](https://leafletjs.com/). "
"Some of its features include: \n"
"- WMS layers \n"
"- Layering \n"
"- Advanced drawing tools \n"
"- Custom baselayers \n"
"- ArcGIS connection \n"
"- Much more... \n"
" \n"
"On the right-hand side an example of a Leaflet map is shown. \n"
" \n"
"**JavaScript? No Python?** \n"
"So actually it is possible to create a Leaflet map with only Python, thanks to "
"[Folium](https://pypi.org/project/folium/). Easy!"
)
wms_details = vkt.Step("WMS set-up", views=["show_wms_details"], on_next=_validate_wms_details)
wms_details.text = vkt.Text(
"# WMS set-up \n"
"In this step we will gather the information required in order to add your own WMS-layer to your map. \n"
"## WMS in Leaflet \n"
"In order to add WMS-layers to Leaflet, we need obtain some information from the WMS-layer first. "
"Luckily, we can all do that within this app, making use of the Python package "
"[OWSLib](https://pypi.org/project/OWSLib/). "
"We need to obtain the base url, layer names and set the format. For more information, check out this "
"[tutorial](https://leafletjs.com/examples/wms/wms.html). \n"
"- **Base url**: The base url is the url of the WMS, without the 'getcapabilities' part. \n"
"- **Layers**: A WMS-layer can contain multiple layers. For example: streets, houses, etc. \n"
"- **Format**: The format in which the layer is retrieved. Most common is png. \n"
"## WMS input \n"
"Please provide the WMS url below. When filled in the base url, layers and name of the WMS layer will appear "
"on the right-hand side. A sample of a WMS url can be used by clicking on the button below. \n"
" \n"
"In the Netherlands, [PDOK](https://www.pdok.nl/datasets) is the main provider of public WMS-layers. A lot"
" of interesting examples can be found on their website. \n"
)
wms_details.set_sample_wms = vkt.SetParamsButton("Use sample WMS", "set_sample_wms")
wms_details.wms_input = vkt.TextAreaField("WMS url", description="Please enter the WMS url here", flex=100)
wms_details.wms_version = vkt.OptionField(
"WMS version",
options=["1.1.1", "1.3.0"],
default="1.3.0",
description="Enter the version of the WMS-layer here. The most common version is version 1.3.0",
)
wms_details.fmt_format = vkt.OptionField(
"Format",
options=["image/png", "image/jpeg"],
default="image/png",
description="Format of data of the WMS. More options are generally available, but to keep it simple only png "
"and jpg are included in this app. Default is png.",
)
wms_map = vkt.Step("Custom WMS", views=["custom_wms_map"])
wms_map.text = vkt.Text(
"# Custom WMS map \n"
"Now everything is set up, the WMS-layer can be added to the map. Just select the layers to display on the map."
" Wait for the map "
"to reload and the result should appear on the map. That's it! \n"
" \n"
"*Note: depending on the WMS-layer you are using, you might need to zoom in or zoom out to see the layer "
"appear on the map.*"
)
wms_map.layer_options = vkt.MultiSelectField("Display layers", options=_get_layer_options)
class Controller(vkt.ViktorController):
"""Controller for the sample Leaflet app."""
viktor_enforce_field_constraints = True # Resolves upgrade instruction https://docs.viktor.ai/sdk/upgrades#U83
label = "WMS map controller"
parametrization = Parametrization
@vkt.WebView("Leaflet sample map", duration_guess=1)
def leaflet_introduction(self, params, **kwargs) -> vkt.WebResult:
"""Create and show a sample leaflet map"""
m = folium.Map(location=[51.922408, 4.4695292], zoom_start=13)
folium.TileLayer(
tiles="https://service.pdok.nl/hwh/luchtfotorgb/wmts/v1_0/Actueel_ortho25/EPSG:3857/{z}/{x}/{y}.jpeg",
attr='Kaartgegevens © <a href="https://www.kadaster.nl">Kadaster</a>',
name="NL luchtfoto 2020",
overlay=False,
control=True,
).add_to(m)
folium.TileLayer(
tiles="https://service.pdok.nl/hwh/luchtfotorgb/wmts/v1_0/2018_ortho25/EPSG:3857/{z}/{x}/{y}.jpeg",
attr='Kaartgegevens © <a href="https://www.kadaster.nl">Kadaster</a>',
name="NL luchtfoto 2018",
overlay=False,
control=True,
).add_to(m)
folium.TileLayer(
tiles="https://stamen-tiles-{s}.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.jpg",
attr='Map tiles by <a href="http://stamen.com">Stamen Design</a>, <a href="http://creativecommons.org/'
'licenses/by/3.0">CC BY 3.0</a> — Map data © <a href="https://www.openstreetmap.org/'
'copyright">OpenStreetMap</a> contributors',
name="Watercolor",
overlay=False,
control=True,
).add_to(m)
folium.raster_layers.TileLayer(
tiles="https://tiles.openseamap.org/seamark/{z}/{x}/{y}.png",
attr='Map data: © <a href="http://www.openseamap.org">OpenSeaMap</a> contributors',
name="OpenSeaMap",
overlay=True,
show=True,
).add_to(m)
folium.raster_layers.TileLayer(
tiles="https://{s}.tiles.openrailwaymap.org/standard/{z}/{x}/{y}.png",
attr='Map data: © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors | '
'Map style: © <a href="https://www.OpenRailwayMap.org">OpenRailwayMap</a> '
'(<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)',
name="OpenRailWay",
overlay=True,
show=False,
).add_to(m)
folium.raster_layers.WmsTileLayer(
url="https://service.pdok.nl/rws/vaarwegmarkeringennld/wms/v1_0",
layers=["markdrijvendrd", "markvastrd"],
transparent=True,
control=True,
fmt="image/png",
name="Vaarwegmarkeringen WMS",
overlay=True,
show=True,
version="1.3.0",
).add_to(m)
draw = Draw(export=True)
draw.add_to(m)
folium.LayerControl().add_to(m)
html_result = vkt.File()
m.save(html_result.source)
return vkt.WebResult(html=html_result)
def set_sample_wms(self, params, **kwargs) -> vkt.SetParamsResult:
"""Fills in the sample WMS to the params"""
return vkt.SetParamsResult({"wms_details": {"wms_input": WMS_DEFAULT}})
@vkt.DataView("WMS details", duration_guess=1)
def show_wms_details(self, params, **kwargs) -> vkt.DataResult:
"""Shows the details of the WMS layer, such as the base url, layers and the name."""
if not params.wms_details.wms_input:
data = vkt.DataGroup(vkt.DataItem("Pleas enter a WMS url", None))
else:
wms = connect_to_WMS(params.wms_details.wms_input, params.wms_details.wms_version)
wms_details = get_WMS_details(wms)
data = vkt.DataGroup(
base_url=vkt.DataItem("Base url", wms_details["Base url"]),
layers=vkt.DataItem(
"Layers",
f"{len(wms_details['Layers'])} layers",
subgroup=vkt.DataGroup(
*[
vkt.DataItem(f"Layer {index}", layer)
for index, layer in enumerate(wms_details["Layers"], start=1)
]
),
),
name=vkt.DataItem("Name", wms_details["Name"]),
)
return vkt.DataResult(data)
@vkt.WebView("Custom WMS map", duration_guess=1)
def custom_wms_map(self, params, **kwargs) -> vkt.WebResult:
"""Creates a map with the WMS-layer, as specified by the user."""
m = folium.Map(location=[51.922408, 4.4695292], zoom_start=13)
wms = connect_to_WMS(params.wms_details.wms_input, params.wms_details.wms_version)
wms_details = get_WMS_details(wms)
wms_layers = params.wms_map.layer_options
folium.raster_layers.WmsTileLayer(
url=wms_details["Base url"],
layers=wms_layers,
transparent=True,
control=True,
fmt=params.wms_details.fmt_format,
name=wms_details["Name"],
overlay=True,
show=True,
version=params.wms_details.wms_version,
).add_to(m)
folium.LayerControl().add_to(m)
html_result = vkt.File()
m.save(html_result.source)
return vkt.WebResult(html=html_result)