1/* 2########################### anthrazit ag ############################ 3### swisstopo / OpenLayers implementation of the gllp lat/lon picker ### 4### Drop-in replacement for the google jquery-gmaps-latlon-picker, ### 5### working on the same .gllpLatlonPicker markup produced by ### 6### getGeoCoordsPicker(): ### 7### .gllpMap .gllpLatitude .gllpLongitude .gllpZoom .gllpIcon ### 8### .gllpstreet .gllpzip .gllpcity + search form / locate button ### 9##################################################################### 10 11Assumes OpenLayers (ol) + SwissMap are already loaded (via loadSwissMapAssets 12or the admin block). Call SwissGllpPicker.initAll() after the markup is in the DOM. 13*/ 14 15(function (global) { 16 'use strict'; 17 18 function num(v, d) { v = parseFloat(v); return isNaN(v) ? d : v; } 19 20 var SwissGllpPicker = { 21 22 init: function (container) { 23 if (typeof ol === 'undefined' || typeof $ === 'undefined') return; 24 var $c = $(container); 25 var mapEl = $c.find('.gllpMap').get(0); 26 if (!mapEl || mapEl._swissInited) return; 27 mapEl._swissInited = true; 28 29 var $lat = $c.find('.gllpLatitude'); 30 var $lng = $c.find('.gllpLongitude'); 31 var lat = num($lat.val(), 46.80121); 32 var lng = num($lng.val(), 8.226692); 33 var zoom = parseInt($c.find('.gllpZoom').val(), 10) || 14; 34 35 var icon = null; 36 var iv = $c.find('.gllpIcon').val(); 37 if (iv) { try { icon = JSON.parse(iv); } catch (e) {} } 38 39 mapEl.innerHTML = ''; 40 mapEl.style.position = 'relative'; 41 if (!mapEl.style.width) mapEl.style.width = '100%'; 42 43 var base = SwissMap.baseLayerForStyle(); 44 var view = new ol.View({ center: ol.proj.fromLonLat([lng, lat]), zoom: zoom, maxZoom: 21 }); 45 var map = new ol.Map({ 46 target: mapEl, 47 layers: [base], 48 view: view, 49 controls: ol.control.defaults.defaults({ zoom: true, attribution: true, rotate: false }) 50 }); 51 SwissMap.addAerialToggle(map, base); 52 53 // draggable pin marker (this is the whole point of the picker) 54 var iconUrl = (icon && icon.url) ? icon.url : '/appl/grfx/marker.svg'; 55 // draw the pin at the configured marker size ($cfg['map_marker_size']), like the other 56 // maps; height scales any icon (custom or fallback) preserving aspect ratio 57 var markerSize = parseInt(mapEl.getAttribute('data-markersize'), 10) || 32; 58 var pin = new ol.Feature(new ol.geom.Point(ol.proj.fromLonLat([lng, lat]))); 59 var pinLayer = new ol.layer.Vector({ 60 source: new ol.source.Vector({ features: [pin] }), 61 zIndex: 10, 62 style: new ol.style.Style({ image: new ol.style.Icon({ src: iconUrl, anchor: [0.5, 1], height: markerSize }) }) 63 }); 64 map.addLayer(pinLayer); 65 66 var revT; 67 function updateFromPin(doReverse) { 68 var c = ol.proj.toLonLat(pin.getGeometry().getCoordinates()); 69 $lat.val(c[1]).trigger('change'); 70 $lng.val(c[0]).trigger('change'); 71 if (doReverse !== false) { 72 clearTimeout(revT); 73 revT = setTimeout(function () { reverse(c[1], c[0]); }, 300); 74 } 75 } 76 77 // drag the pin 78 var translate = new ol.interaction.Translate({ features: new ol.Collection([pin]) }); 79 translate.on('translateend', function () { updateFromPin(); }); 80 map.addInteraction(translate); 81 82 // click on the map moves the pin there 83 map.on('singleclick', function (evt) { 84 pin.getGeometry().setCoordinates(evt.coordinate); 85 updateFromPin(); 86 }); 87 88 // pointer cursor when hovering the pin 89 map.on('pointermove', function (evt) { 90 if (evt.dragging) return; 91 var hit = map.hasFeatureAtPixel(evt.pixel, { layerFilter: function (l) { return l === pinLayer; } }); 92 map.getTargetElement().style.cursor = hit ? 'move' : ''; 93 }); 94 95 // move pin + recentre (used by search / locate) 96 function placePin(lon, la) { 97 pin.getGeometry().setCoordinates(ol.proj.fromLonLat([lon, la])); 98 view.animate({ center: ol.proj.fromLonLat([lon, la]), zoom: Math.max(zoom, 17), duration: 300 }); 99 updateFromPin(); 100 } 101 102 // swisstopo reverse geocode -> fill street / zip / city 103 function reverse(la, lo) { 104 fetch('https://geodesy.geo.admin.ch/reframe/wgs84tolv95?easting=' + lo + '&northing=' + la + '&format=json') 105 .then(function (r) { return r.json(); }) 106 .then(function (p) { 107 if (!p || !p.easting) return; 108 var e = p.easting, n = p.northing; 109 var url = 'https://api3.geo.admin.ch/rest/services/api/MapServer/identify' 110 + '?geometryType=esriGeometryPoint&returnGeometry=false&tolerance=50&sr=2056&lang=de' 111 + '&layers=all:ch.bfs.gebaeude_wohnungs_register' 112 + '&geometry=' + e + ',' + n 113 + '&imageDisplay=100,100,96' 114 + '&mapExtent=' + (e - 50) + ',' + (n - 50) + ',' + (e + 50) + ',' + (n + 50); 115 return fetch(url).then(function (r) { return r.json(); }).then(function (d) { 116 var a = d && d.results && d.results[0] && d.results[0].attributes; 117 if (!a) return; 118 $c.find('.gllpstreet').val(a.strname_deinr || ''); 119 $c.find('.gllpzip').val(a.dplz4 || '');
120 $c.find('.gllpcity').val(a.dplzname || a.ggdename || ''); 121 }); 122 }).catch(function () {}); 123 } 124 125 // swisstopo forward search -> move pin + recentre 126 function search(q) { 127 if (!q) return; 128 fetch('https://api3.geo.admin.ch/rest/services/api/SearchServer?type=locations&sr=4326&limit=1&searchText=' + encodeURIComponent(q)) 129 .then(function (r) { return r.json(); }) 130 .then(function (d) { 131 var res = d && d.results && d.results[0]; 132 if (res && res.attrs) placePin(res.attrs.lon, res.attrs.lat); 133 }).catch(function () {}); 134 } 135 $c.find('.gllpSearchButton').on('click', function () { search($c.find('.gllpSearchField').val()); }); 136 $c.find('.gllpSearchField').on('keypress', function (e) { if (e.keyCode === 13) { e.preventDefault(); search($(this).val()); } }); 137 $c.find('.gllpLocateButton').on('click', function () { 138 if (navigator.geolocation) navigator.geolocation.getCurrentPosition(function (pos) { 139 placePin(pos.coords.longitude, pos.coords.latitude); 140 }); 141 }); 142 143 // draw bfs (municipality) polygons when a bfs number set is defined 144 if (typeof bfsNumbers !== 'undefined' && bfsNumbers) { 145 $.post('/appl/api.php?do=getBfsPolygon', { bfsnr: bfsNumbers }, function (data) { 146 if (!data || !data.length) return; 147 var feats = [], ext = ol.extent.createEmpty(); 148 for (var i = 0; i < data.length; i++) { 149 var ring = []; 150 for (var j = 0; j < data[i].length; j++) { 151 ring.push(ol.proj.fromLonLat([data[i][j][1], data[i][j][0]])); // [lat,lng] -> [lon,lat] 152 } 153 var poly = new ol.Feature(new ol.geom.Polygon([ring])); 154 feats.push(poly); 155 ol.extent.extend(ext, poly.getGeometry().getExtent()); 156 } 157 map.addLayer(new ol.layer.Vector({ 158 source: new ol.source.Vector({ features: feats }), 159 zIndex: 5, 160 style: new ol.style.Style({ 161 stroke: new ol.style.Stroke({ color: 'rgba(17,106,212,0.6)', width: 1 }), 162 fill: new ol.style.Fill({ color: 'rgba(17,106,212,0.25)' }) 163 }) 164 })); 165 if (!ol.extent.isEmpty(ext)) view.fit(ext, { padding: [20, 20, 20, 20] }); 166 }, 'json'); 167 } 168 169 global.setTimeout(function () { map.updateSize(); updateFromPin(false); }, 300); 170 if (global.ResizeObserver) new global.ResizeObserver(function () { map.updateSize(); }).observe(mapEl); 171 }, 172 173 initAll: function () { 174 if (typeof $ === 'undefined') return; 175 $('.gllpLatlonPicker').each(function () { SwissGllpPicker.init(this); }); 176 } 177 }; 178 179 global.SwissGllpPicker = SwissGllpPicker; 180 181})(window);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.