8b08cb4552d7b84188f0ac8281f1ce3ced9b0ee7
[philipp/winterrodeln/mediawiki_extensions/wrmap.git] / wrmap.js
1 "use strict";
2
3 function init_wrmap(i, jq_map) {
4         // define constants
5         let EPSG4326 = ol.proj.get("EPSG:4326"); // lon/lat
6         let EPSG3857 = ol.proj.get("EPSG:3857"); // google
7
8         // tool functions
9         function createElement(tagName, attributes) {
10                 let element = $(document.createElement(tagName));
11                 if (attributes === undefined) return element;
12                 for (let attribute in attributes) {
13                         element.attr(attribute, attributes[attribute]);
14                 }
15                 return element;
16         }
17
18         function appendElement(parentElement, tagName, attributes) {
19                 if (attributes === undefined) attributes = {};
20                 let element = createElement(tagName, attributes);
21                 parentElement.append(element);
22                 return element;
23         }
24
25
26         // extract geojson from map element and clear map element's content
27         jq_map = $(jq_map);
28         let ext_path = jq_map.attr('data-ext-path'); // e.g. '/mediawiki/extensions/wrmap'
29         let img_path = ext_path + '/img';
30         let json_string = jq_map.children().last().text();
31         jq_map.empty(); // once parsed, remove geojson string from the map element.
32         let json_js = JSON.parse(json_string);
33         let format_geojson = new ol.format.GeoJSON();
34         let features_all = format_geojson.readFeatures(json_js, {dataProjection: EPSG4326, featureProjection: EPSG3857});
35
36
37         // path layer
38         // ----------
39
40         function get_feature_title(feature) {
41                 let title = feature.get('type');
42                 if (title == 'sledrun') return feature.get('name');
43                 title = title.charAt(0).toUpperCase() + title.slice(1); // first letter uppercase
44                 if (feature.get('name')) title += ': ' + feature.get('name');
45                 return title;
46         }
47
48         // Returns 0 to 5 for features that represent sledruns as their condition
49         let get_sledrun_condition = function(feature) {
50                 let condition = feature.get('condition');
51                 if (condition === undefined) return 0;
52                 return condition;
53         }
54
55         function sledrun_icon_style(condition, highlight) {
56                 let hl = highlight ? 'h' : 'n';
57                 let src = img_path + '/marker_c_sledrun_' + condition + 'n' + hl + '.png';
58                 return new ol.style.Style({
59                         image: new ol.style.Icon({
60                                 src: src,
61                                 imgSize: [17, 17],
62                                 anchor: [0.5, 0.5]
63                         }),
64                 });
65
66         }
67
68         function sledrun_icon_shadow_style() {
69                 return new ol.style.Style({
70                         image: new ol.style.Icon({
71                                 src: img_path + '/marker_c_shadow.png',
72                                 imgSize: [23, 23],
73                                 anchor: [0.4, 0.4]
74                         }),
75                 });
76         }
77
78         function marker_icon_style(feature) {
79                 let src = img_path + '/marker_p_' + feature.get('type') + '.png';
80                 return new ol.style.Style({
81                         image: new ol.style.Icon({
82                                 src: src,
83                                 imgSize: [20, 34],
84                                 anchor: [0.5, 1.0]
85                         }),
86                 });
87         }
88
89         function point_style(feature, highlight) {
90                 let sledrun = feature.get('type') == 'sledrun';
91                 let icon_style;
92                 if (sledrun) {
93                         let condition = get_sledrun_condition(feature);
94                         icon_style = sledrun_icon_style(condition, highlight);
95                 } else icon_style = marker_icon_style(feature);
96                 if (highlight) {
97                         icon_style.setText(new ol.style.Text({
98                                 text: get_feature_title(feature),
99                                 font: 'icon',
100                                 offsetY: 14,
101                                 stroke: new ol.style.Stroke({
102                                         color: '#ddd',
103                                         width: 2,
104                                 }),
105                         }));
106                 }
107                 if (sledrun) {
108                         let shadow_style = sledrun_icon_shadow_style();
109                         return [shadow_style, icon_style];
110                 }
111                 return [icon_style];
112         }
113
114         function style_point_function(feature, resolution) {
115                 return point_style(feature, false);
116         }
117
118         function style_point_function_highlight(feature, resolution) {
119                 return point_style(feature, true);
120         }
121
122         function style_path_function(feature, resolution) {
123                 let line_color = {
124                         'rodelbahn': '#014e9a',
125                         'gehweg': '#e98401',
126                         'alternative': '#7f7fff',
127                         'lift': '#000000',
128                         'anfahrt': '#e1e100'
129                 };
130                 let color = feature.get('strokeColor') || line_color[feature.get('type')] || '#e7525b';
131                 let width = (feature.get('type') in ['lift', 'anfahrt']) ? 3 : 6;
132                 return new ol.style.Style({
133                         stroke: new ol.style.Stroke({
134                                 color: color,
135                                 width: width
136                         })
137                 });
138         }
139
140         function style_function(feature, resolution) {
141                 if (feature.getGeometry() instanceof ol.geom.Point) return style_point_function(feature, resolution);
142                 return style_path_function(feature, resolution);
143         };
144
145         function style_function_highlight(feature, resolution) {
146                 if (feature.getGeometry() instanceof ol.geom.Point) return style_point_function_highlight(feature, resolution);
147                 return style_path_function(feature, resolution);
148         };
149
150
151         // popup overlay
152         // -------------
153         let popup_container = document.getElementById('popup');
154         let popup_content = document.getElementById('popup-content');
155         let popup_closer = document.getElementById('popup-closer');
156         let popup_overlay = new ol.Overlay({element: popup_container, autoPan: {animation: {duration: 250}}});
157         popup_closer.onclick = function() {popup_overlay.setPosition(undefined); popup_closer.blur(); return false;};
158
159         function create_popup_dom(feature) {
160                 let popup_div = createElement('div');
161
162                 // name
163                 if (feature.get('name') !== undefined && (feature.get('wiki') !== undefined || feature.get('thumb_url') !== undefined)) {
164                         let h2 = appendElement(popup_div, 'h2');
165                         if (feature.get('wiki') === undefined) h2.text(feature.get('name'));
166                         else appendElement(h2, 'a', {href: feature.get('wiki')}).text(feature.get('name'));
167                 }
168
169                 // sledrun information
170                 if (feature.get('type') == 'sledrun') {
171                         let p = appendElement(popup_div, 'p').text('Rodelbahnzustand').append(createElement('br'));
172                         if (feature.get('condition') !== undefined) {
173                                 let condition_text = {1: 'Sehr gut', 2: 'Gut', 3: 'Mittelmäßig', 4: 'Schlecht', 5: 'Geht nicht'};
174                                 let year_month_day = feature.get('date_report').split('-');
175                                 p.append(createElement('a', {href: feature.get('wiki') + '#Eintr.C3.A4ge'}).text(condition_text[feature.get('condition')]), ' ');
176                                 p.append(createElement('small').text(year_month_day[2] + '.' + year_month_day[1] + '.'), ' ');
177                                 p.append(createElement('em').append(createElement('a', {href: feature.get('wiki') + '#Eintragen'}).text('Neu')));
178                         } else {
179                                 p.append(createElement('em').append(createElement('a', {href: feature.get('wiki') + '#Eintragen'}).text('Bitte eintragen')));
180                         }
181                 }
182
183                 // wiki link
184                 if (feature.get('wiki') !== undefined) {
185                         let a = appendElement(appendElement(popup_div, 'p'), 'a', {href: feature.get('wiki')});
186                         let detail_text = 'Details';
187                         if (feature.get('type') == 'sledrun') detail_text += ' zur Rodelbahn';
188                         if (feature.get('type') == 'gasthaus') detail_text += ' zum Gasthaus';
189                         if (feature.get('thumb_url') === undefined) a.text(detail_text);
190                         else a.append(createElement('img', {src: feature.get('thumb_url'), alt: detail_text, title: detail_text}));
191                 }
192
193                 return popup_div;
194         }
195
196
197         // map itself
198         // ----------
199         let lon = json_js.properties.lon;
200         let lat = json_js.properties.lat;
201         let zoom = json_js.properties.zoom;
202         let width = json_js.properties.width;
203         let height = json_js.properties.height;
204         if (zoom === undefined) zoom = 10; // default zoom
205         if (width === undefined) width = '100%'; // default width
206         if (height === undefined) height = 450;  // default: 450 pixel
207         jq_map.width(width);
208         jq_map.height(height);
209
210         let layer_sledrun_source = new ol.source.Vector({features: features_all});
211         let layer_sledrun = new ol.layer.Vector({
212                 source: layer_sledrun_source,
213                 style: style_function
214         });
215
216         let map = new ol.Map({
217                 target: jq_map[0],
218                 layers: [
219                         layer_sledrun
220                 ],
221                 overlays: [popup_overlay],
222                 view: new ol.View({
223                         center: ol.proj.fromLonLat([lon, lat]),
224                         zoom: zoom
225                 }),
226                 controls: ol.control.defaults({
227                         attributionOptions: {
228                                 collapsible: false
229                         }
230                 }),
231                 interactions: ol.interaction.defaults({
232                         mouseWheelZoom: false
233                 })
234         });
235
236
237         let select_hover = new ol.interaction.Select({
238                 condition: ol.events.condition.pointerMove,
239                 style: style_function_highlight,
240         });
241         map.addInteraction(select_hover);
242
243         let select_click = new ol.interaction.Select({
244                 condition: ol.events.condition.click,
245                 style: false,
246         });
247         map.addInteraction(select_click);
248         select_click.on('select', function(event) {
249                 if (event.selected.length > 0) {
250                         let feature = event.selected[0];
251                         let coordinates = feature.getGeometry().getCoordinates();
252                         let popup_dom = create_popup_dom(feature);
253                         if (popup_dom.children().length > 0) {
254                                 $(popup_content).empty().append(popup_dom);
255                                 popup_overlay.setPosition(coordinates);
256                         }
257                 }
258         });
259
260
261         // background layer
262         // ----------------
263         function get_austria_feature() {
264                 // the following polygon is too coarse to be useful but still nice for debugging
265                 let austria_wkt = 'Polygon((16.97 48.12, 16.90 47.71, 16.34 47.71, 16.53 47.49, 16.20 46.85, 16.01 46.68, 15.13 46.65, 14.63 46.43, 13.80 46.50, 12.37 46.76, 12.15 47.11, 11.16 46.94, 11.04 46.75, 10.44 46.89, 9.93 46.92, 9.47 47.10, 9.63 47.34, 9.59 47.52, 9.89 47.58, 10.40 47.30, 10.54 47.56, 11.42 47.52, 12.14 47.70, 12.62 47.67, 12.93 47.46, 13.02 47.63, 12.88 48.28, 13.24 48.41, 13.59 48.87, 14.33 48.55, 14.90 48.96, 15.25 49.03, 16.02 48.73, 16.49 48.78, 16.96 48.59, 16.87 48.47, 16.97 48.12))';
266                 let format = new ol.format.WKT();
267                 let feature = format.readFeature(austria_wkt, {
268                         dataProjection: EPSG4326,
269                         featureProjection: EPSG3857,
270                 });
271                 return feature;
272         }
273
274         // basemap.at layer
275         let austria_feature = get_austria_feature();
276         function is_in_austria(feature) {
277                 return austria_feature.getGeometry().intersectsCoordinate(feature.getGeometry().getFirstCoordinate());
278         }
279         if (features_all.every(is_in_austria)) {
280                 let capabilitiesUrl = 'https://www.basemap.at/wmts/1.0.0/WMTSCapabilities.xml';
281                 fetch(capabilitiesUrl).then(function(response) {
282                         return response.text();
283                 }).then(function(text) {
284                         let result = new ol.format.WMTSCapabilities().read(text);
285                         let options = ol.source.WMTS.optionsFromCapabilities(result, {
286                                 layer: 'geolandbasemap',
287                                 matrixSet: 'google3857',
288                                 style: 'normal',
289                         });
290                         let layer_map = new ol.layer.Tile({
291                                 source: new ol.source.WMTS(options),
292                         });
293                         map.getLayers().insertAt(0, layer_map);
294                 });
295         } else {
296                 let layer_map = new ol.layer.Tile({
297                         source: new ol.source.OSM()
298                 });
299                 map.getLayers().insertAt(0, layer_map);
300         }
301
302         // // Alternatives:
303         // // * OpenTopoMap (see https://opentopomap.org/about)
304         // // * OSM
305         // let layer_map = new ol.layer.Tile({
306         //     source: new ol.source.OSM()
307         // });
308         // map.getLayers().insertAt(0, layer_map);
309 }
310
311
312 function init_wrmaps() {
313         let jq_maps = $('.wrmap'); // all wrmap <div> elements
314         jq_maps.each(init_wrmap);
315 }
316
317
318 $(document).ready(init_wrmaps);