├── .gitignore ├── README.md ├── TimedependentPolygonmapTBWidget ├── Readme.md ├── argos_maps.json ├── maputils.json ├── polyimagemap.js ├── pubsubGeoJSON.py ├── pubsubGeoJSONmulti.1.py ├── pubsubGeoJSONmulti.py └── pubsubWind.py ├── js ├── PolygonMap.js └── PolygonMap_orig.js └── widgets ├── PolygonMap.js ├── TimeseriesPolygonImageMap.js ├── TimeseriesPolygonMap.js └── newWidget.js /.gitignore: -------------------------------------------------------------------------------- 1 | TimedependentPolygonmapTBWidget/.env/ 2 | *.pyc 3 | *.pem -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Thingsboard-widgets 2 | Additional widgets for the Argos project 3 | 4 | ## Time dependent polygon map widget 5 | ------------------------------------- 6 | 7 | This widget displays polygons in different time slices. 8 | For details see [here](https://github.com/argosp/argos/wiki/A-TB-widget-to-display-a-dispersion-model-results) 9 | 10 | See original [TbMapWidgetV2](https://github.com/thingsboard/thingsboard/blob/master/ui/src/app/widget/lib/map-widget2.js) and [image-map](https://github.com/thingsboard/thingsboard/blob/master/ui/src/app/widget/lib/image-map.js) 11 | -------------------------------------------------------------------------------- /TimedependentPolygonmapTBWidget/Readme.md: -------------------------------------------------------------------------------- 1 | # Timedependent Polygon map TBWidget 2 | ------------------------------------ 3 | 4 | A first version of the Timedependent Polygon map TBWidget. 5 | 6 | The widget assumes that the attribute of the device contain a single geoJSON. 7 | 8 | To setup the example: 9 | 10 | 1. Install anaconda3, paho package. 11 | 1. Create a device in Thingsboard and change its token to A1_TEST_TOKEN. (The string in the pubsubJSON.py, line 88). 12 | 2. Change the IP of the server in pubsubJSON.py to be that of the server. 13 | 3. Create a new dashboard and define an alias of a single device. Choose the device. 14 | 4. Add 3 attributes A,B and C (they do not exist until you have published with the pubsub code, so either publish a new 15 | few messages or click on the create new attribute and change the name to A,B or C. 16 | 17 | 5. The pubsubJSON.py publishes the subtraction of the polygons from each other to maintain the colors. 18 | 19 | Alternately to anaconda: 20 | 21 | ``` 22 | cd TimedependentPolygonmapTBWidget 23 | python3 -m venv .env 24 | source .env/bin/activate 25 | pip install paho-mqtt numpy geopandas 26 | python -m pubsubGeoJSONmulti.py 27 | ``` 28 | -------------------------------------------------------------------------------- /TimedependentPolygonmapTBWidget/argos_maps.json: -------------------------------------------------------------------------------- 1 | { 2 | "widgetsBundle": { 3 | "alias": "eran", 4 | "title": "Argos-maps", 5 | "image": null 6 | }, 7 | "widgetTypes": [ 8 | { 9 | "alias": "number", 10 | "name": "TimeseriesPolygonMap", 11 | "descriptor": { 12 | "type": "latest", 13 | "sizeX": 7.5, 14 | "sizeY": 5.5, 15 | "resources": [ 16 | { 17 | "url": "" 18 | } 19 | ], 20 | "templateHtml": "
\n \n \n \n \n \n \n \n \n \n \n
\n \n \n \n \n \n \n \n
\n \n \n \n \n \n \n
\n
\n\n\n", 21 | "templateCss": "#container {\n overflow: auto;\n}\n\n.tbDatasource-container {\n margin: 5px;\n padding: 8px;\n}\n\n.tbDatasource-title {\n font-size: 1.200rem;\n font-weight: 500;\n padding-bottom: 10px;\n}\n\n.tbDatasource-table {\n width: 100%;\n box-shadow: 0 0 10px #ccc;\n border-collapse: collapse;\n white-space: nowrap;\n font-size: 1.000rem;\n color: #757575;\n}\n\n.tbDatasource-table td {\n position: relative;\n border-top: 1px solid rgba(0, 0, 0, 0.12);\n border-bottom: 1px solid rgba(0, 0, 0, 0.12);\n padding: 0px 18px;\n box-sizing: border-box;\n}", 22 | "controllerScript": "self.onInit = function() {\n var map = $('#TimeseriesPolygonMap');\n self.ctx.map = new TbMapWidgetV2('openstreet-map',\n false, self.ctx, undefined, map);\n self.mapleaflet = self.ctx.map.map.map;\n window.TimeseriesPolygonMapSelf = self;\n //self.mapleaflet.panBy([200, 300]); \n self.mapleaflet.setView([32.7, 34.95], 9);\n // self.resizeMap();\n // console.log(self);\n}\n\nself.resizeMap = function() {\n self.mapleaflet._container.style.height = (self.ctx\n .height - 30) + \"px\";\n}\n\nself.clearPolygons = function() {\n if (!self.mapleaflet._layers) return;\n for (var i in self.mapleaflet._layers) {\n if (self.mapleaflet._layers[i]._path !==\n undefined) {\n try {\n self.mapleaflet.removeLayer(self.mapleaflet\n ._layers[i]);\n } catch (e) {\n console.log(\"problem with \" + e + self.mapleaflet\n ._layers[i]);\n }\n\n }\n }\n\n}\n\nfunction parseJson(json) {\n var value = String(json).replace(/'/g, '\\\"');\n return JSON.parse(value);\n}\nself.showPolygons = function(parsed, color) {\n try {\n geojsonFeature = parsed.features;\n } catch (err) {\n return;\n // geojsonFeature = null\n }\n var myStyle = {\n \"color\": color,\n \"opacity\": 0.8\n };\n try {\n L.geoJSON(geojsonFeature, {\n style: myStyle\n }).addTo(self.mapleaflet);\n } catch (e) {\n console.log(e);\n }\n\n};\n\nself.updateTimeRange = function(parsed) {\n var maxRange = 0;\n for (i = 0; i < parsed.length; i++) {\n // var prop = self.ctx.data[i].data[0][1];\n // console.log(prop);\n for (j = 0; j < parsed[i].length; j++) {\n if (parsed[i][j].index > maxRange) {\n maxRange = parsed[i][j].index;\n }\n }\n }\n $('#TimeFrame')[0].max = maxRange;\n};\n\nfunction findCurrFrame(frames, index) {\n if (frames instanceof Array) {\n for (var i = 0; i < frames.length; ++i) {\n if (parseInt(frames[i].index) === index) {\n return frames[i];\n }\n }\n }\n return undefined;\n}\n\nself.getCurrIndex = function() {\n return parseInt($('#TimeFrame')[0].value);\n}\nself.setCurrIndex = function(val) {\n // console.log('setCurrIndex', val);\n $('#TimeFrame')[0].value = val;\n self.onDataUpdated();\n}\nself.onDataUpdated = function() {\n var parsed = self.ctx.data.map(prop => parseJson(\n prop.data[0][1]));\n self.updateTimeRange(parsed);\n var index = self.getCurrIndex();\n self.clearPolygons();\n // console.log(self.ctx.data);\n var valueName = undefined;\n for (i = 0; i < self.ctx.data.length; i++) {\n var poly = findCurrFrame(parsed[i], index);\n if (!poly) continue;\n valueName = poly.name;\n var color = self.ctx.data[i].dataKey.color;\n self.showPolygons(poly.value, color);\n }\n $('#TimeFrameLabel')[0].value = valueName ?\n valueName : '';\n self.ctx.map.update();\n}\n\nself.setNext = function() {\n var maxRange = parseInt($('#TimeFrame')[0].max);\n if (maxRange >= 1) {\n self.setCurrIndex((self.getCurrIndex() + 1) % (\n maxRange + 1));\n } else {\n self.setCurrIndex(0);\n }\n}\n\nself.Play = function() {\n self.playing = !self.playing\n $('#PlayButton').text(self.playing ? 'Stop' :\n 'Play');\n clearInterval(self.playInterval);\n if (self.playing) {\n var delay = self.ctx.settings.playDelay || 500;\n self.playInterval = setInterval((function() {\n self.setNext()\n }).bind(this), delay);\n }\n}\n\nself.onResize = function() {\n self.ctx.map.resize();\n // self.resizeMap();\n}\n\nself.getSettingsSchema = function() {\n var tbScheme = TbMapWidgetV2.settingsSchema(\n 'openstreet-map');\n return tbScheme;\n}\nself.getDataKeySettingsSchema = function() {\n return TbMapWidgetV2.dataKeySettingsSchema(\n 'openstreet-map');\n}\nself.actionSources = function() {\n return TbMapWidgetV2.actionSources();\n}\nself.onDestroy = function() {}", 23 | "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"playDelay\": {\n \"title\": \"Play Delay\",\n \"type\": \"number\",\n \"default\": 500\n }\n }\n },\n \"form\": [\n \"playDelay\"\n ]\n}", 24 | "dataKeySettingsSchema": "{}\n", 25 | "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"TimeseriesPolygonMap\"}" 26 | } 27 | }, 28 | { 29 | "alias": "polygonmap", 30 | "name": "PolygonMap", 31 | "descriptor": { 32 | "type": "latest", 33 | "sizeX": 7.5, 34 | "sizeY": 3, 35 | "resources": [], 36 | "templateHtml": "", 37 | "templateCss": "#container {\n overflow: auto;\n}\n\n.tbDatasource-container {\n margin: 5px;\n padding: 8px;\n}\n\n.tbDatasource-title {\n font-size: 1.200rem;\n font-weight: 500;\n padding-bottom: 10px;\n}\n\n.tbDatasource-table {\n width: 100%;\n box-shadow: 0 0 10px #ccc;\n border-collapse: collapse;\n white-space: nowrap;\n font-size: 1.000rem;\n color: #757575;\n}\n\n.tbDatasource-table td {\n position: relative;\n border-top: 1px solid rgba(0, 0, 0, 0.12);\n border-bottom: 1px solid rgba(0, 0, 0, 0.12);\n padding: 0px 18px;\n box-sizing: border-box;\n}", 38 | "controllerScript": "self.onInit = function() {\n self.ctx.map = new TbMapWidgetV2('openstreet-map', false, self.ctx);\n self.mapleaflet = self.ctx.map.map.map;\n //self.mapleaflet.panBy([200, 300]); \n self.mapleaflet.setView([32, 34.95], 9);\n}\n\nself.onDataUpdated = function() {\n \n for (var i in self.mapleaflet._layers) {\n if (self.mapleaflet._layers[i]._path !==\n undefined) {\n try {\n self.mapleaflet.removeLayer(self.mapleaflet\n ._layers[i]);\n } catch (e) {\n console.log(\"problem with \" + e + self.mapleaflet._layers[i]);\n }\n\n }\n }\n \n for (i = 0; i < self.ctx.data.length; i++) {\n var dataitem = self.ctx.data[i];\n var value = String(dataitem.data[0][1]).replace(/'/g, '\\\"');\n var parsed = JSON.parse(value);\n try {\n geojsonFeature = parsed.features;\n\n } catch (err) {\n geojsonFeature = null\n }\n var myStyle = {\n \"color\": dataitem.dataKey.color, \n \"opacity\": 0.8 \n };\n try {\n L.geoJSON(geojsonFeature, {\n style: myStyle\n }).addTo(self.mapleaflet);\n } catch (e) {\n console.log(e);\n }\n self.ctx.map.update(); \n \n }\n}\nself.onResize = function() {\n self.ctx.map.resize();\n}\nself.getSettingsSchema = function() {\n var tbScheme = TbMapWidgetV2.settingsSchema(\n 'openstreet-map');\n return tbScheme;\n}\nself.getDataKeySettingsSchema = function() {\n return TbMapWidgetV2.dataKeySettingsSchema('openstreet-map');\n}\nself.actionSources = function() {\n return TbMapWidgetV2.actionSources();\n}\nself.onDestroy = function() {}\n", 39 | "settingsSchema": "{}", 40 | "dataKeySettingsSchema": "{}\n", 41 | "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"PolygonMap\"}" 42 | } 43 | }, 44 | { 45 | "alias": "image_map", 46 | "name": "TimeseriesPolygonImageMap", 47 | "descriptor": { 48 | "type": "latest", 49 | "sizeX": 9, 50 | "sizeY": 9, 51 | "resources": [ 52 | { 53 | "url": "" 54 | } 55 | ], 56 | "templateHtml": "
\n \n \n \n \n \n \n \n \n \n \n
\n \n \n \n \n \n \n \n
\n \n \n \n \n \n \n
\n
\n", 57 | "templateCss": ".leaflet-zoom-box {\n\tz-index: 9;\n}\n\n.leaflet-pane { z-index: 4; }\n\n.leaflet-tile-pane { z-index: 2; }\n.leaflet-overlay-pane { z-index: 4; }\n.leaflet-shadow-pane { z-index: 5; }\n.leaflet-marker-pane { z-index: 6; }\n.leaflet-tooltip-pane { z-index: 7; }\n.leaflet-popup-pane { z-index: 8; }\n\n.leaflet-map-pane canvas { z-index: 1; }\n.leaflet-map-pane svg { z-index: 2; }\n\n.leaflet-control {\n\tz-index: 9;\n}\n.leaflet-top,\n.leaflet-bottom {\n\tz-index: 11;\n}\n\n.tb-marker-label {\n border: none;\n background: none;\n box-shadow: none;\n}\n\n.tb-marker-label:before {\n border: none;\n background: none;\n}\n", 58 | "controllerScript": "self.onInit = function () {\n var elemap = $('#TimeseriesPolygonImageMap');\n self.ctx.map = new TbMapWidgetV2('image-map', false, self.ctx, undefined, elemap);\n self.posFunc = self.ctx.map.map.posFunction;\n setTimeout(() => {\n self.mapleaflet = self.ctx.map.map.map;\n window.TimeseriesPolygonImageMapSelf = self;\n self.onResize();\n self.onDataUpdated();\n\n console.log(self);\n }, 10);\n};\n\nself.resizeMap = function () {\n if (!self.mapleaflet || !self.mapleaflet._container) {\n return;\n }\n self.mapleaflet._container.style.height = (self.ctx.height - 30) + \"px\";\n self.mapleaflet.invalidateSize();\n};\n\nself.clearPolygons = function () {\n if (!self.mapleaflet || !self.mapleaflet._layers) {\n return;\n }\n for (var i in self.mapleaflet._layers) {\n if (self.mapleaflet._layers[i]._path !== undefined) {\n try {\n self.mapleaflet.removeLayer(self.mapleaflet._layers[i]);\n } catch (e) {\n console.log(\"problem with \" + e + self.mapleaflet._layers[i]);\n }\n }\n }\n};\n\nfunction parseJson(json) {\n var value = String(json).replace(/'/g, '\\\"');\n return JSON.parse(value);\n}\n\nfunction lerp(a, b, f) {\n return a + f * (b - a);\n}\n\nself.posOnMap = function (v) {\n const bounds = self.ctx.map.map.imageOverlay._bounds;\n var ne = bounds._northEast,\n sw = bounds._southWest;\n var pos = self.posFunc(v[0], v[1]);\n pos.y = lerp(ne.lat, sw.lat, pos.y);\n pos.x = lerp(sw.lng, ne.lng, pos.x);\n return [pos.x, pos.y];\n};\n\nself.posFuncGeoJson = function (geojson) {\n geojson.forEach((g) => {\n g.geometry.coordinates = g.geometry.coordinates.map(shape => shape.map(self.posOnMap));\n });\n return geojson;\n};\n\nself.showPolygons = function (parsed, color) {\n try {\n var geojsonFeature = self.posFuncGeoJson(parsed.features);\n L.geoJSON(geojsonFeature, {\n style: { \"color\": color, \"opacity\": 0.8 }\n }).addTo(self.mapleaflet);\n } catch (e) {\n console.log(e);\n }\n};\n\nself.getCurrIndex = function () {\n return parseInt($('#TimeFrame')[0].value);\n};\n\nself.setCurrIndex = function (val) {\n $('#TimeFrame')[0].value = val;\n self.onDataUpdated();\n};\n\nself.getPolygonFrames = function () {\n const jsons = self.ctx.data.filter(prop => prop.data.length > 0 && typeof prop.data[0][1] === \"string\");\n var maxRange = 0;\n const polygonFrames = jsons.map(prop => {\n const frames = parseJson(prop.data[0][1]);\n maxRange = Math.max(Math.max.apply(this, frames.map(b => b.index)));\n return { frames: frames, color: prop.dataKey.color };\n });\n return [polygonFrames, maxRange];\n};\n\nself.showPolygonFrame = function (polygonFrames, index) {\n let valueName = '';\n self.clearPolygons();\n polygonFrames.forEach(polys => {\n const poly = polys.frames.find(frame => parseInt(frame.index) === index);\n if (poly) {\n valueName = poly.name;\n self.showPolygons(poly.value, polys.color);\n }\n });\n self.ctx.map.update();\n return valueName ? valueName : '';\n};\n\nself.getDirectionTelemetry = function () {\n // console.log(self.ctx.data);\n const nonJsons = self.ctx.data.filter(prop => prop.data.length > 0 && typeof prop.data[0][1] !== \"string\");\n let ret = {};\n nonJsons.forEach(prop => {\n const t = prop.datasource.name;\n ret[t] = ret[t] || {};\n const kind = prop.dataKey.name;\n ret[t][kind] = prop.data[0][1];\n });\n return Object.values(ret);\n};\n\nself.showDirectionMarker = function (s) {\n if (s.latitude === undefined || s.longitude === undefined) return undefined;\n let dir = s[self.ctx.settings.keyNameDir];\n if (dir === undefined) return undefined;\n let power = s[self.ctx.settings.keyNamePower];\n if (power === undefined) power = 4;\n const powerSize = parseFloat(power) * self.ctx.settings.ArrowLenAt4 / 4;\n if (/(Win32|Win64)/i.test(navigator.platform)) dir = (dir + 180) % 360;\n\n const pos = self.posOnMap([s.latitude, s.longitude]);\n var marker = L.marker([pos[1], pos[0]], {\n icon: L.icon({\n iconUrl: self.ctx.settings.arrowImageUrl,\n iconSize: [powerSize, parseFloat(self.ctx.settings.ArrowWidth)]\n }),\n rotationAngle: 360 - dir % 360, // counter clockwise\n rotationOrigin: 'center'\n }).addTo(self.mapleaflet);\n\n return marker;\n};\n\nself.onDataUpdated = function () {\n try {\n const index = self.getCurrIndex();\n [polygonFrames, maxRange] = self.getPolygonFrames();\n $('#TimeFrame')[0].max = maxRange;\n $('#TimeFrameLabel')[0].value = self.showPolygonFrame(polygonFrames, index);\n } catch (err) {\n // console.log(\"JSON error:\", self.ctx.data, err);\n // return;\n }\n\n (self.windMarkers || []).forEach(m => m.removeFrom(self.mapleaflet));\n const dirs = self.getDirectionTelemetry();\n self.windMarkers = dirs.map(self.showDirectionMarker);\n self.windMarkers = self.windMarkers.filter(a => a);\n};\n\nself.setNext = function () {\n var maxRange = parseInt($('#TimeFrame')[0].max);\n if (maxRange >= 1) {\n self.setCurrIndex((self.getCurrIndex() + 1) % (maxRange + 1));\n } else {\n self.setCurrIndex(0);\n }\n};\n\nself.Play = function () {\n self.playing = !self.playing;\n $('#PlayButton').text(self.playing ? 'Stop' : 'Play');\n if (self.playing) {\n var cont = () => {\n self.setNext();\n if (self.playing) {\n setTimeout(cont, self.ctx.settings.playDelay);\n }\n };\n cont();\n }\n};\n\nself.onResize = function () {\n self.ctx.map.resize();\n self.resizeMap();\n};\n\nself.getSettingsSchema = function () {\n var tbScheme = JSON.parse(JSON.stringify(TbMapWidgetV2.settingsSchema('image-map')));\n // console.log(tbScheme);\n tbScheme.form.unshift(\n \"playDelay\",\n \"keyNameDir\",\n \"keyNamePower\",\n \"ArrowLenAt4\",\n \"ArrowWidth\",\n {\n \"key\": \"arrowImageUrl\",\n \"type\": \"image\"\n }\n );\n Object.assign(tbScheme.schema.properties,\n {\n \"playDelay\": {\n \"title\": \"Play delay\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"keyNameDir\": {\n \"title\": \"Direction key name\",\n \"type\": \"string\",\n \"default\": \"wind_dir\"\n },\n \"keyNamePower\": {\n \"title\": \"Power key name\",\n \"type\": \"string\",\n \"default\": \"wind_speed\"\n },\n \"ArrowLenAt4\": {\n \"title\": \"Arrow length in pixels when power/speed is 4\",\n \"type\": \"number\",\n \"default\": 40\n },\n \"ArrowWidth\": {\n \"title\": \"Arrow width in pixels\",\n \"type\": \"number\",\n \"default\": 40\n },\n \"arrowImageUrl\": {\n \"title\": \"Arrow image\",\n \"type\": \"string\",\n \"default\": \"data:image/png;base64,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\"\n }\n });\n return tbScheme;\n};\nself.getDataKeySettingsSchema = function () {\n return TbMapWidgetV2.dataKeySettingsSchema('image-map');\n};\n\nself.actionSources = function () {\n return TbMapWidgetV2.actionSources();\n};\n\nself.onDestroy = function () { };\n\n(function () {\n // save these original methods before they are overwritten\n var proto_initIcon = L.Marker.prototype._initIcon;\n var proto_setPos = L.Marker.prototype._setPos;\n\n var oldIE = (L.DomUtil.TRANSFORM === 'msTransform');\n\n L.Marker.addInitHook(function () {\n var iconOptions = this.options.icon && this.options.icon.options;\n var iconAnchor = iconOptions && this.options.icon.options.iconAnchor;\n if (iconAnchor) {\n iconAnchor = (iconAnchor[0] + 'px ' + iconAnchor[1] + 'px');\n }\n this.options.rotationOrigin = this.options.rotationOrigin || iconAnchor || 'center bottom';\n this.options.rotationAngle = this.options.rotationAngle || 0;\n\n // Ensure marker keeps rotated during dragging\n this.on('drag', function (e) { e.target._applyRotation(); });\n });\n\n L.Marker.include({\n _initIcon: function () {\n proto_initIcon.call(this);\n },\n\n _setPos: function (pos) {\n proto_setPos.call(this, pos);\n this._applyRotation();\n },\n\n _applyRotation: function () {\n if (this.options.rotationAngle) {\n this._icon.style[L.DomUtil.TRANSFORM + 'Origin'] = this.options.rotationOrigin;\n\n // console.log(this.options.rotationAngle);\n // if(oldIE) {\n // // for IE 9, use the 2D rotation\n // this._icon.style[L.DomUtil.TRANSFORM] = 'rotate(' + this.options.rotationAngle + 'deg)';\n // } else {\n // // for modern browsers, prefer the 3D accelerated version\n this._icon.style[L.DomUtil.TRANSFORM] =\n this._icon.style[L.DomUtil.TRANSFORM].replace(/ rotateZ\\(\\d+deg\\)/g, '')\n + ' rotateZ(' + this.options.rotationAngle + 'deg)';\n // }\n }\n },\n\n setRotationAngle: function (angle) {\n this.options.rotationAngle = angle;\n this.update();\n return this;\n },\n\n setRotationOrigin: function (origin) {\n this.options.rotationOrigin = origin;\n this.update();\n return this;\n }\n });\n})();\n", 59 | "settingsSchema": "{}", 60 | "dataKeySettingsSchema": "{}\n", 61 | "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"First point\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"xPos\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.05427416942713381,\"funcBody\":\"var value = prevValue || 0.2;\\nif (time % 5000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"yPos\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.680594833308841,\"funcBody\":\"var value = prevValue || 0.3;\\nif (time % 5000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"temperature\",\"color\":\"#9c27b0\",\"settings\":{},\"_hash\":0.9430343126300238,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"type\",\"color\":\"#8bc34a\",\"settings\":{},\"_hash\":0.1784452363910778,\"funcBody\":\"return \\\"colorpin\\\";\"}]},{\"type\":\"function\",\"name\":\"Second point\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"xPos\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.05012157428742059,\"funcBody\":\"var value = prevValue || 0.6;\\nif (time % 4000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"yPos\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.6742359401617628,\"funcBody\":\"var value = prevValue || 0.7;\\nif (time % 4000 < 500) {\\n value += Math.random() * 0.05 - 0.025;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"temperature\",\"color\":\"#8bc34a\",\"settings\":{},\"_hash\":0.773875863339494,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"type\",\"color\":\"#3f51b5\",\"settings\":{},\"_hash\":0.405822538899673,\"funcBody\":\"return \\\"thermomether\\\";\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showLabel\":true,\"label\":\"${entityName}\",\"tooltipPattern\":\"${entityName}

X Pos: ${xPos:2}
Y Pos: ${yPos:2}
Temperature: ${temperature} °C
See advanced settings for details\",\"markerImageSize\":34,\"useColorFunction\":true,\"markerImages\":[\"data:image/png;base64,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\",\"data:image/png;base64,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\",\"data:image/png;base64,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\",\"data:image/png;base64,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\"],\"useMarkerImageFunction\":true,\"colorFunction\":\"var type = dsData[dsIndex]['type'];\\nif (type == 'colorpin') {\\n\\tvar temperature = dsData[dsIndex]['temperature'];\\n\\tif (typeof temperature !== undefined) {\\n\\t var percent = (temperature + 60)/120 * 100;\\n\\t return tinycolor.mix('blue', 'red', amount = percent).toHexString();\\n\\t}\\n\\treturn 'blue';\\n}\\n\",\"markerImageFunction\":\"var type = dsData[dsIndex]['type'];\\nif (type == 'thermomether') {\\n\\tvar res = {\\n\\t url: images[0],\\n\\t size: 40\\n\\t}\\n\\tvar temperature = dsData[dsIndex]['temperature'];\\n\\tif (typeof temperature !== undefined) {\\n\\t var percent = (temperature + 60)/120;\\n\\t var index = Math.min(3, Math.floor(4 * percent));\\n\\t res.url = images[index];\\n\\t}\\n\\treturn res;\\n}\",\"color\":\"#fe7569\",\"mapImageUrl\":\"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgoKPHN2ZwogICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiCiAgIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIKICAgeG1sbnM6c3ZnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxuczpzb2RpcG9kaT0iaHR0cDovL3NvZGlwb2RpLnNvdXJjZWZvcmdlLm5ldC9EVEQvc29kaXBvZGktMC5kdGQiCiAgIHhtbG5zOmlua3NjYXBlPSJodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy9uYW1lc3BhY2VzL2lua3NjYXBlIgogICB3aWR0aD0iMTEzNC41MTgzIgogICBoZWlnaHQ9Ijc2Mi43ODI0MSIKICAgaWQ9InN2ZzIiCiAgIHZlcnNpb249IjEuMSIKICAgaW5rc2NhcGU6dmVyc2lvbj0iMC40OC41IHIxMDA0MCIKICAgc29kaXBvZGk6ZG9jbmFtZT0id2ljaGl0YW1hcC1ub2xpYi5zdmciPgogIDxkZWZzCiAgICAgaWQ9ImRlZnM0IiAvPgogIDxzb2RpcG9kaTpuYW1lZHZpZXcKICAgICBpZD0iYmFzZSIKICAgICBwYWdlY29sb3I9IiNmZmZmZmYiCiAgICAgYm9yZGVyY29sb3I9IiM2NjY2NjYiCiAgICAgYm9yZGVyb3BhY2l0eT0iMS4wIgogICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwLjAiCiAgICAgaW5rc2NhcGU6cGFnZXNoYWRvdz0iMiIKICAgICBpbmtzY2FwZTp6b29tPSIwLjM1IgogICAgIGlua3NjYXBlOmN4PSI4OS45MDc4NTciCiAgICAgaW5rc2NhcGU6Y3k9IjQ1My43ODI0MSIKICAgICBpbmtzY2FwZTpkb2N1bWVudC11bml0cz0icHgiCiAgICAgaW5rc2NhcGU6Y3VycmVudC1sYXllcj0ibGF5ZXIxIgogICAgIHNob3dncmlkPSJmYWxzZSIKICAgICBpbmtzY2FwZTp3aW5kb3ctd2lkdGg9IjEzNjYiCiAgICAgaW5rc2NhcGU6d2luZG93LWhlaWdodD0iNzIxIgogICAgIGlua3NjYXBlOndpbmRvdy14PSItNCIKICAgICBpbmtzY2FwZTp3aW5kb3cteT0iLTQiCiAgICAgaW5rc2NhcGU6d2luZG93LW1heGltaXplZD0iMSIKICAgICBpbmtzY2FwZTpvYmplY3QtcGF0aHM9InRydWUiCiAgICAgaW5rc2NhcGU6c25hcC1nbG9iYWw9ImZhbHNlIgogICAgIHNob3dndWlkZXM9InRydWUiCiAgICAgaW5rc2NhcGU6Z3VpZGUtYmJveD0idHJ1ZSIKICAgICBmaXQtbWFyZ2luLXRvcD0iMCIKICAgICBmaXQtbWFyZ2luLWxlZnQ9IjAiCiAgICAgZml0LW1hcmdpbi1yaWdodD0iMCIKICAgICBmaXQtbWFyZ2luLWJvdHRvbT0iMCIgLz4KICA8bWV0YWRhdGEKICAgICBpZD0ibWV0YWRhdGE3Ij4KICAgIDxyZGY6UkRGPgogICAgICA8Y2M6V29yawogICAgICAgICByZGY6YWJvdXQ9IiI+CiAgICAgICAgPGRjOmZvcm1hdD5pbWFnZS9zdmcreG1sPC9kYzpmb3JtYXQ+CiAgICAgICAgPGRjOnR5cGUKICAgICAgICAgICByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIiAvPgogICAgICAgIDxkYzp0aXRsZT48L2RjOnRpdGxlPgogICAgICA8L2NjOldvcms+CiAgICA8L3JkZjpSREY+CiAgPC9tZXRhZGF0YT4KICA8ZwogICAgIGlua3NjYXBlOmxhYmVsPSJMYXllciAxIgogICAgIGlua3NjYXBlOmdyb3VwbW9kZT0ibGF5ZXIiCiAgICAgaWQ9ImxheWVyMSIKICAgICB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjcuMDcxNDI4LC0zMDcuOTAyOTkpIj4KICAgIDxwYXRoCiAgICAgICBpZD0icGF0aDM3ODciCiAgICAgICBzdHlsZT0iZmlsbDpub25lO3N0cm9rZTojMzY0ZTU5O3N0cm9rZS13aWR0aDoyLjk5OTk5OTc2O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLW9wYWNpdHk6MTtzdHJva2UtZGFzaGFycmF5Om5vbmUiCiAgICAgICBkPSJtIDkwNi4wMzMxNSw3MDYuMTMzNjcgMy40MjkyLDE3Ljc5NTUyIE0gMjguNTcxNDI4LDc2NS4wNTA2NyBjIDE1MC40MzUyMDIsNi44MzM0MiAxNDYuMzkyMzIyLC0yNi4zMzQxNSAxNjYuNDM0NTQyLC0yOS4zMjAwOSAzNi4xNDM3NSwtNS4zODQ3NiAxMTQuMjg2NzYsLTYuNTI1NCAxNDguMzI1MDgsLTguNjIzNTQgNDMuMzc4MDgsLTIuNjczODUgMTQxLjc2MjIxLC0xMS4yMzA5OSAxODguODU1NzgsLTE5LjgzNDE4IDM5LjgxMTM4LC03LjI3Mjg0IDIyMS4zNjk5MSwtMC44NjIzNSAzMTkuMDcxNDEsLTAuODYyMzUgNzAuODI3MzUsMCAxNDYuOTE4NjcsLTEuNzI0NyAyMTguMTc1ODYsLTEuNzI0NyAtMzEuNjE5NywwIDExNy44NTUyLC0yLjU4NzA3IDg2LjIzNTUsLTIuNTg3MDcgbSAtMjUuMDkwNywtNjguMTI2MDYgYyAtNTIuNzk5NiwzNC43ODQ4NCAtNjUuODk1MSw1MS43NDg2NSAtOTUuNjM5LDgxLjQ5MjU4IC0yNC45MzEzLDI0LjkzMTI3IC0xNDAuMzk2NTMsLTE5LjEzOTIgLTE3OC45Mzg3MSwzNi42NTAwNyAtMTIuMjgxNCwxNy43NzcxNSAtNDcuMDAyNTcsNDYuNTQ2NTMgLTY1LjEwNzgzLDU5LjA3MTMzIC0yMC4xMDUsMTMuOTA4MTggLTU2LjAzNjcyLDQ0Ljk1NjY0IC02Ny43Njg4NSw3My4wNzgyNyAtNC44MDE0NywxMS41MDkwMiAtMTMuMzgwNDYsMzUuOTkyOTggLTIzLjQ0OTQ5LDQ2LjA2MjAxIC0xMC40OTY5OSwxMC40OTY5OSAtMzguMzc3MzMsNi4zODU2OSAtNDQuMDIzNDUsMTcuNjQ3NjQgLTE5LjAwNTAyLDM3LjkwODEyIC0yNS40NjUzLDEwMC45MjM1MiAtNjcuNjE3ODksMTAyLjA1MTAyIG0gMTkuMjgxNTEsLTYyNC4wMTQ2NCBjIDM0LjY1OTM0LC0xLjg3MzgyIDg0LjAyNzMzLDcuMzkxMzEgMTA5LjkwMDcxLC00LjI4NTQ1IDEzLjI4MTcyLC01Ljk5NDA4IDQxLjQwNzIxLC0yLjQ2MTM1IDY2LjgyODY2LC0yLjMyMDQ2IDM1LjMyMjM4LDAuMTk1NzggNjQuMzgyNDksMC42MzQ3NyAxMDEuOTE2Nyw1LjAyMzIgMjUuMDMwMzYsMi45MjY1IDQ0LjY2MjczLDM0LjI4NzIyIDU4LjUyNjk4LDUwLjY0MzkgMTcuMDk4NzgsMjAuMTcyNjggNjIuNzYzODYsLTEuNzE0NjcgNjYuMzA1NjYsMzIuMTM0MzMgNS4xMDI3LDQ4Ljc2NTg3IC02LjMyODQsNzguNjM3MjUgNi4xNDExLDk3LjM0MTUgMTkuOTY5MiwyOS45NTM3OSA1MC40ODY0LDE3Ljg1NTc5IDQ0LjYxOTMsODMuOTcxMTkgTSA1ODkuMTAyMjcsMzA5LjcyNzE1IGMgNC42NDM0NiwyMy43MjkyMyAxNS4wNjkwNCw3Mi43NzU3NSAxOS4wNjEyOCwxMzAuNjQyODggMC44NzIwNiwxMi42NDA0OCA1LjQ0NzE4LDI0Ljk5MjUzIDQuMjIyMzEsNDUuMjc3NTcgLTIuNTE3MjEsNDEuNjg3NSAtMTUuNzE3MDYsNDMuNjc3MjcgLTE1LjA5MTIyLDYwLjM2NDg2IDEuNDMxOTUsMzguMTgyMjQgMzAuNjEzNjEsOTMuODM3MTkgMzAuNjEzNjEsMTM5LjcwMTU0IDAsMjQuMTgwOCAtMi42Njk2NCwxMTUuMzkwNDUgNy4zMzAwMSwxMzUuMzg5NzYgMC4xNTkxMSwwLjMxODIxIDEwLjA2NDc2LDM1Ljg4MzMyIDEwLjc3OTQ1LDQ5LjE1NDI0IDAuOTQzNzgsMTcuNTI0NjkgLTI0LjQ3OCwzOS40NzAwOCAtMjguMDI2NTUsNDYuNTY3MTYgLTUuNDc3NywxMC45NTUzOSAtMzYuOTczMjQsMTAuODgxOTcgLTQwLjA5OTUsMjQuMTQ1OTUgLTMuODY4ODQsMTYuNDE0NTEgLTMuODY2Myw0My43OTczNSA0LjA0NjQ3LDU5LjQ0MTI5IG0gOTcuMzM3MzQsLTY5MS4wMDk0MSBjIC01LjAxMzMyLDM1LjUxNTk1IC00My42NTkwMSwxMS4zMTY1MiAtNTguNTM4NjEsMjMuNzgxMzEgLTIxLjMzMDE5LDE3Ljg2ODUyIC02Mi40OTk2NCwzMS40MzIxMiAtNzAuMTI0MzcsMzUuMzY3MDggLTM1LjA4NzYzLDE4LjEwNzkzIC0xMTAuNDcyMTUsLTE1LjE0MTk2IC0xMjUuNjE0MSw0LjI2ODQzIC0xNS45NTA2MywyMC40NDcwMyAtMC4wNzM1LDYxLjQ2NjQ4IC05LjE0NjY2LDg0LjE0OTI0IC02LjAzNTcsMTUuMDg5MjYgLTE4Ljg3NjcsMjMuMDE3MzQgLTI3LjQzOTk3LDMyLjkyNzk4IC0xOS43NDgyOSwyMi44NTU1NSAtNjkuOTc0MjgsNjkuODI0MTkgLTg0Ljc1OTA0LDEwMC4wMDM0NiAtNy40OTc0MSwxNS4zMDQwNCAtMy4yODQyNiw0NC40MjA0MSAtMy40NzA1Myw2My4zNDI4NCAtMC4xMjc5MywxMi45OTQxNCAtMC44MTAxNSwyMy4xMDM4NSAyLjQwMzQzLDI4LjI3NjE4IDQuOTYxNTgsNy45ODU4MSAyMy43MjA1LDI4LjExMjA3IDI0LjIzODY1LDUwLjYxMTQ5IDAuMjk0MTEsMTIuNzcxNDYgMC4wMTMzLDc4LjU5MTAxIDMuMDQ4ODgsODcuNjU1NDkgMi4zMTI1Niw2LjkwNTQ2IDQuMjIwMDQsMjYuNTY0OTcgMTAuMjEzNzcsMzYuNTg2NjIgMTEuMzU0MDEsMTguOTg0MTUgNC4zODczNyw0MC4xNTY2MiAyNy44OTczLDUzLjUwNzk1IDE5LjA1MDEyLDEwLjgxODU5IDQ2Ljg3NzgxLDEyLjIxODYyIDgxLjkyNjE4LDE0LjQ2MDU0IDMzLjcwMzQ1LDIuMTU1ODkgNjEuNTEyMTcsLTEuNDMwMzUgNzYuOTIwNzcsNi4xNDExIDExLjU4NTA4LDUuNjkyNjYgOC41ODE1MSwxNy45MzM0NCAxNC4yOTU0MSwyOS4zNjEyMyA1LjY0MDQyLDExLjI4MDg1IDMxLjUwMjYzLDExLjE1NjI3IDQxLjgwNDA5LDQzLjQ1NDg3IDcuNjA1OSwyMy44NDcxIDMuMDg1OTMsNDQuMTU2OSA2LjcwNzU1LDY1Ljg4NjYiCiAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgc29kaXBvZGk6bm9kZXR5cGVzPSJjY2Nzc3NzY2Njc3Nzc3NzY2Nzc3Nzc3NjY3Nzc3Njc3NzY2Nzc3Nzc3Nzc3Nzc3Nzc3NzYyIgLz4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iZmlsbDpub25lO3N0cm9rZTojMzMzMzY2O3N0cm9rZS13aWR0aDoxcHg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW9wYWNpdHk6MSIKICAgICAgIGQ9Im0gNDMuMjc3ODgxLDUxNy45NDY3OSBjIDAsMCAyMzAuODQ4Mjg5LC0zLjYzODA1IDI1MC4wMDg2MzksLTMuNjU4NjcgNy40ODIyMiwtMC4wMDggOC42MTk1NCw1LjE1MTk0IDE0LjAyMDksMTEuNDU4NjkgMjQuNTk2MDgsMjguNzE4OTMgOTMuOTA5NjYsMTEyLjkzNTg1IDkzLjkwOTY2LDExMi45MzU4NSIKICAgICAgIGlkPSJwYXRoMzc4OSIKICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICBzb2RpcG9kaTpub2RldHlwZXM9ImNzc2MiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6IzMzMzM2NjtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1vcGFjaXR5OjEiCiAgICAgICBkPSJtIDM1Ljk2MDU1NSw1NzcuNzA0OTQgYyAwLDAgMTY1LjUyNDU2NSwtMS42ODQ1NCAyNDguNzc5NTY1LC0xLjY4NDU0IDQuOTQ3NDksMCA3LjcyOTkzLC0yLjg4MzMgMTAuNTM3NzEsLTUuNzI5NzcgOS42NjEwNywtOS43OTQxNiAyNS42MzE5OSwtMjguNTg5OTUgMjUuNjMxOTksLTI4LjU4OTk1IgogICAgICAgaWQ9InBhdGgzNzkxIgogICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY3NzYyIgLz4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iY29sb3I6IzAwMDAwMDtmaWxsOiMzMzMzNjY7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOiMzMzMzNjY7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1vcGFjaXR5OjE7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1kYXNob2Zmc2V0OjA7bWFya2VyOm5vbmU7dmlzaWJpbGl0eTp2aXNpYmxlO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7ZW5hYmxlLWJhY2tncm91bmQ6YWNjdW11bGF0ZSIKICAgICAgIGQ9Ik0gMzguMzk5NjYzLDY0MS43MzE1NSA0MzEuNzA1OTMsNjM3LjQ2MzExIgogICAgICAgaWQ9InBhdGgzNzk1IgogICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIgLz4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iY29sb3I6IzAwMDAwMDtmaWxsOiMzMzMzNjY7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOiMzMzMzNjY7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1vcGFjaXR5OjE7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1kYXNob2Zmc2V0OjA7bWFya2VyOm5vbmU7dmlzaWJpbGl0eTp2aXNpYmxlO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7ZW5hYmxlLWJhY2tncm91bmQ6YWNjdW11bGF0ZSIKICAgICAgIGQ9Ik0gMzkuMDA5NDQyLDcwNC41Mzg1OSA1MjMuMTcyNTMsNjk3LjgzMTA0IgogICAgICAgaWQ9InBhdGgzNzk3IgogICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIgLz4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iY29sb3I6IzAwMDAwMDtmaWxsOm5vbmU7c3Ryb2tlOiMzMzMzNjY7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1vcGFjaXR5OjE7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1kYXNob2Zmc2V0OjA7bWFya2VyOm5vbmU7dmlzaWJpbGl0eTp2aXNpYmxlO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7ZW5hYmxlLWJhY2tncm91bmQ6YWNjdW11bGF0ZSIKICAgICAgIGQ9Im0gMzAzLjk1NzYyLDY4Mi41ODY2MSAxNDYuNzk1NDIsMS44MjkzMyBjIDEwLjUzNDAzLDAuMTMxMjcgMTQuMzQzNzQsLTIuNjM3MzkgMjUuNDg3MTUsLTYuMzcyOCAxMC40MTIxMiwtMy40OTAyNyAzMS40MjQxNSwtMi42OTg5NiA0MS4zODUzOCwtMi43NzM4NSBsIDQwNS41NjA3OSwtMy4wNDg5IgogICAgICAgaWQ9InBhdGgzNzk5IgogICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY3Nzc2MiIC8+CiAgICA8cGF0aAogICAgICAgaWQ9InBhdGgzODA0IgogICAgICAgc3R5bGU9ImNvbG9yOiMwMDAwMDA7ZmlsbDpub25lO3N0cm9rZTojMzMzMzY2O3N0cm9rZS13aWR0aDoxcHg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW1pdGVybGltaXQ6NDtzdHJva2Utb3BhY2l0eToxO3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2UtZGFzaG9mZnNldDowO21hcmtlcjpub25lO3Zpc2liaWxpdHk6dmlzaWJsZTtkaXNwbGF5OmlubGluZTtvdmVyZmxvdzp2aXNpYmxlO2VuYWJsZS1iYWNrZ3JvdW5kOmFjY3VtdWxhdGUiCiAgICAgICBkPSJtIDQyNi4yMTc5NCwzMTQuODkwOTggYyAyLjA2NzU0LDkuMDUyNzMgMS44NDE3Nyw1MS43Mjc3NyA2LjUwNzk0LDc0LjgzNDY2IDEuNjc0NzUsOC4yOTMzNiA4LjY3NTA4LDE0LjA2NTk4IDEwLjA1NTQxLDE0Ljg1ODYyIDQuOTAxNDcsMi44MTQ2MyAxMC44MTQ3OSw4LjE0OTgyIDEzLjA0NTc5LDE2LjA4ODMxIDYuNzU3NzksMjQuMDQ1OTEgMC44Nzk3Miw2OC40NTIxMiAwLjg3OTcyLDExMC42ODkzIDAsNi4wOTc4MiAxLjY2MDEsMzAuMTQ2NiAtMi4xNTU4OCwzMy45NjI1OSAtMi41NDA4NSwyLjU0MDgzIC0wLjI4MTYzLDEyLjk5MDY5IC0zLjQzNjc1LDE2LjE0Mzc3IGwgLTkuODQ5NDQsOS44NDMxMSBjIC0xMC4zNjcxNSwxMC4zNjA0NyAtMTEuNTkwMTcsNi41MjYxNCAtMTcuNzM4NDgsMTguODIyNzYgLTMuNTY3NzIsNy4xMzU0MyA1LjQwMjM1LDIwLjY3MjEgNy4zNTQzMiwyNC41NzYwMiAxLjkzMjE0LDMuODY0MyAtMS44NDIxNiw0Ljc3NzczIC0xLjc5MjM1LDcuNDQ2MjYgMC4yNTI4NiwxMy41NDQ4MyAyLjI5NzUsMzczLjkyNzEyIDIuMjk3NSwzNzMuOTI3MTIiCiAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgc29kaXBvZGk6bm9kZXR5cGVzPSJjc3Nzc3Nzc3Nzc2MiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImNvbG9yOiMwMDAwMDA7ZmlsbDpub25lO3N0cm9rZTojMzMzMzY2O3N0cm9rZS13aWR0aDoxcHg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW1pdGVybGltaXQ6NDtzdHJva2Utb3BhY2l0eToxO3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2UtZGFzaG9mZnNldDowO21hcmtlcjpub25lO3Zpc2liaWxpdHk6dmlzaWJsZTtkaXNwbGF5OmlubGluZTtvdmVyZmxvdzp2aXNpYmxlO2VuYWJsZS1iYWNrZ3JvdW5kOmFjY3VtdWxhdGUiCiAgICAgICBkPSJtIDM2NS4yNDAyMiw1MTkuNzc2MTIgNC4xMTU5OSw1MDIuMTUxNTgiCiAgICAgICBpZD0icGF0aDM4MDYiCiAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIiAvPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJjb2xvcjojMDAwMDAwO2ZpbGw6bm9uZTtzdHJva2U6IzMzMzM2NjtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLW9wYWNpdHk6MTtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLWRhc2hvZmZzZXQ6MDttYXJrZXI6bm9uZTt2aXNpYmlsaXR5OnZpc2libGU7ZGlzcGxheTppbmxpbmU7b3ZlcmZsb3c6dmlzaWJsZTtlbmFibGUtYmFja2dyb3VuZDphY2N1bXVsYXRlIgogICAgICAgZD0ibSAxMTYuNTMxNjUsNTA0LjE4Njk5IDMuODgwNTksMzEwLjk2NDM2IgogICAgICAgaWQ9InBhdGgzODMxIgogICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIgLz4KICAgIDxwYXRoCiAgICAgICBpZD0icGF0aDM4ODkiCiAgICAgICBzdHlsZT0iY29sb3I6IzAwMDAwMDtmaWxsOm5vbmU7c3Ryb2tlOiMzMzMzNjY7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1vcGFjaXR5OjE7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1kYXNob2Zmc2V0OjA7bWFya2VyOm5vbmU7dmlzaWJpbGl0eTp2aXNpYmxlO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7ZW5hYmxlLWJhY2tncm91bmQ6YWNjdW11bGF0ZSIKICAgICAgIGQ9Im0gMzE3LjY3NzYsNTc2LjQ4NTM5IDEzMC4xODc0MiwxLjUyNDQ0IGMgNC41MTA3OSwzLjI0MTY5IDIwLjM0NDcxLDcuOTY4NTMgMjcuNzQ0ODYsNC4yNjg0NCAzLjE1NTQ2LC0xLjU3NzcyIDkuNDE5LC01LjM4ODE3IDE0LjAyNDg5LC0zLjk2MzU1IDQuMjY2OTgsMS4zMTk4MSA2LjAxNjg5LDMuMTE2MzIgMTAuMzY2MjEsMy4wNDg4OSAxMC4zMDQwMywtMC4xNTk3NSAyMC4yMTE3LDAuMzg3NDEgMzAuNDg4ODYsMC4zMDQ4OSAxNzcuODkwOCwtMS40MjgyNyAzNTYuNTkwMzUsLTIuMTMyNDcgNTM0Ljc3NDU2LC0zLjA0ODg4IgogICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY2Nzc3NzYyIgLz4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iY29sb3I6IzAwMDAwMDtmaWxsOm5vbmU7c3Ryb2tlOiMzMzMzNjY7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1vcGFjaXR5OjE7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1kYXNob2Zmc2V0OjA7bWFya2VyOm5vbmU7dmlzaWJpbGl0eTp2aXNpYmxlO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7ZW5hYmxlLWJhY2tncm91bmQ6YWNjdW11bGF0ZSIKICAgICAgIGQ9Im0gNDc1LjMwNTAxLDU4Mi44ODgwNSBjIC0zLjQ0NDE4LDExLjM1MDY2IC0yLjEwMzQzLDEyLjQzMzczIDMuNjU4NjUsMjEuMDM3MzEgMy43OTQ0NSw1LjY2NTY0IDUwLjg2MjYxLDEzLjAzODQ1IDQxLjQ2NDg1LDI3LjEzNTA5IC0xMC41MzY5NywxNS44MDU0NyAtMjIuODk3NDUsLTUuNDc3NzIgLTMzLjg0MjYzLC0xLjgyOTMzIC01LjQ1MjM2LDEuODE3NDUgLTcuMzQ5MDEsNS40NTYzMSAtMy42NTg2Niw5LjE0NjY1IDIuODA2ODMsMi44MDY4NCA0LjA0OCwxLjgwMzk2IDYuNTIwMzQsNS4xMDA0MSIKICAgICAgIGlkPSJwYXRoMzkxMCIKICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICBzb2RpcG9kaTpub2RldHlwZXM9ImNzc3NzYyIgLz4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iY29sb3I6IzAwMDAwMDtmaWxsOm5vbmU7c3Ryb2tlOiMzMzMzNjY7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1vcGFjaXR5OjE7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1kYXNob2Zmc2V0OjA7bWFya2VyOm5vbmU7dmlzaWJpbGl0eTp2aXNpYmxlO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7ZW5hYmxlLWJhY2tncm91bmQ6YWNjdW11bGF0ZSIKICAgICAgIGQ9Im0gNDMyLjAxMDgyLDYzNi44NTMzMyBjIDguMzE4OTksMTMuMTEwMTYgMTguODQ2MjEsMTQuNjM0NjUgMzUuNjcxOTYsMTQuNjM0NjUgMi45Mzg2NSwwIDcuODY5OTgsLTAuOTMzNzEgMTAuNjcxMTEsMCAxMS4zNTkxNywzLjc4NjM5IDI3LjE5Mzk4LDEwLjI3NTc3IDM2LjIwMTkzLDIxLjEyOTQ4IDguMjgwMDIsOS45NzY2MSAxMC4yNTI3OCwyMy44ODMwOCA3LjcwMjAyLDM3LjEwNDI0IC02LjE2OTg5LDMxLjk3OTk4IC0xNi43MTQzMSw1Ni45ODg1MyAtMTkuMDQzNTUsODYuNTY5MDUgLTEuMzQ3OTgsMTcuMTE4OCA0LjUwOTU3LDIyLjUzNTIyIDExLjA3MTQzLDMzLjkyODU3IDEwLjY3MDIzLDE4LjUyNjcyIDguNzI0NTMsMTQuMTk5NTUgOC41NzE0MywzNC4yODU3MiAtMC4xMzk2MywxOC4zMTk0NCAwLDYwLjI2Mzg1IDAsODAuNzE0MjkiCiAgICAgICBpZD0icGF0aDM5MTIiCiAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgc29kaXBvZGk6bm9kZXR5cGVzPSJjc3Nzc3Nzc2MiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImNvbG9yOiMwMDAwMDA7ZmlsbDpub25lO3N0cm9rZTojMzMzMzY2O3N0cm9rZS13aWR0aDoxcHg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW1pdGVybGltaXQ6NDtzdHJva2Utb3BhY2l0eToxO3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2UtZGFzaG9mZnNldDowO21hcmtlcjpub25lO3Zpc2liaWxpdHk6dmlzaWJsZTtkaXNwbGF5OmlubGluZTtvdmVyZmxvdzp2aXNpYmxlO2VuYWJsZS1iYWNrZ3JvdW5kOmFjY3VtdWxhdGUiCiAgICAgICBkPSJtIDUyOC41MDgwNiw2NTguOTU3NzYgYyAtMTAuNjgxMjMsMC45MDQ1NCAtNy4xMDgwNCwtNS42MDI1NSAtMTAuODIzNTQsLTguMDc5NTYgLTQuNzg0NTQsLTMuMTg5NjkgLTEyLjIyNzA0LC0xLjI1MTA0IC0xNi43Njg4OCwtNS43OTI4OCAtMC42NjYxMiwtMC42NjYxMiAtOC44MDk2OSwtNC4xMDg3NyAtMTAuMTc0NDcsLTIuNzQzOTkgLTguMzY0NTksOC4zNjQ1OSAtMy4wNDg4OCwyMC41NTE4OCAtMy4wNDg4OCwzMy41Mzc3NCBsIDMuMDIyLDMzOS42OTc0MyIKICAgICAgIGlkPSJwYXRoMzkxNCIKICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICBzb2RpcG9kaTpub2RldHlwZXM9ImNzc3NjIiAvPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJjb2xvcjojMDAwMDAwO2ZpbGw6bm9uZTtzdHJva2U6IzMzMzM2NjtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLW9wYWNpdHk6MTtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLWRhc2hvZmZzZXQ6MDttYXJrZXI6bm9uZTt2aXNpYmlsaXR5OnZpc2libGU7ZGlzcGxheTppbmxpbmU7b3ZlcmZsb3c6dmlzaWJsZTtlbmFibGUtYmFja2dyb3VuZDphY2N1bXVsYXRlIgogICAgICAgZD0ibSA1MTcuOTg5NDEsNjUxLjAzMDY1IGMgLTAuMjIxNzEsLTIuNzAxODQgMS45MDM0NiwtNS41NjIxMyAzLjM1Mzc3LC03LjAxMjQ1IDEuNzk5NDMsLTEuNzk5NDIgNi45MjI5NCwxLjAwNDE5IDguODQxNzgsLTAuOTE0NjYgMC4yODc2NSwtMC4yODc2NiAwLjg0MzI5LC0xMS4xNjQxIDAuMjI4NjYsLTEzLjU2NzUzIC0yLjA2NDgzLC04LjA3NDE2IC0yLjA1ODAxLC0yOC42NTY1OCAtMi4wNTgwMSwtMzguNzIwODYgbCAwLC03My4xNzMyNiIKICAgICAgIGlkPSJwYXRoMzkxNiIKICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICBzb2RpcG9kaTpub2RldHlwZXM9ImNzY3NzYyIgLz4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iY29sb3I6IzAwMDAwMDtmaWxsOm5vbmU7c3Ryb2tlOiMzMzMzNjY7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1vcGFjaXR5OjE7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1kYXNob2Zmc2V0OjA7bWFya2VyOm5vbmU7dmlzaWJpbGl0eTp2aXNpYmxlO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7ZW5hYmxlLWJhY2tncm91bmQ6YWNjdW11bGF0ZSIKICAgICAgIGQ9Im0gNTI4LjY2MDUsNjc1LjQyMTczIC0wLjQ1NzMzLC0zMS41NTU5NiIKICAgICAgIGlkPSJwYXRoMzk3NCIKICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImNvbG9yOiMwMDAwMDA7ZmlsbDpub25lO3N0cm9rZTojMzMzMzY2O3N0cm9rZS13aWR0aDoxcHg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW1pdGVybGltaXQ6NDtzdHJva2Utb3BhY2l0eToxO3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2UtZGFzaG9mZnNldDowO21hcmtlcjpub25lO3Zpc2liaWxpdHk6dmlzaWJsZTtkaXNwbGF5OmlubGluZTtvdmVyZmxvdzp2aXNpYmxlO2VuYWJsZS1iYWNrZ3JvdW5kOmFjY3VtdWxhdGUiCiAgICAgICBkPSJtIDc2Ni4zMTYyNSw1NzkuNjQ0MzEgMC40MzExOCwxMy43OTc2OCBjIDMuMTM2NDMsNC42NjkxNSAzLjAxODI0LDkuNjAwNjggMy4wMTgyNCwxNi4zODQ3NSBsIDAsMTU3LjM3OTgxIgogICAgICAgaWQ9InBhdGgzOTgyIgogICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIgLz4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iY29sb3I6IzAwMDAwMDtmaWxsOm5vbmU7c3Ryb2tlOiMzMzMzNjY7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1vcGFjaXR5OjE7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1kYXNob2Zmc2V0OjA7bWFya2VyOm5vbmU7dmlzaWJpbGl0eTp2aXNpYmxlO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7ZW5hYmxlLWJhY2tncm91bmQ6YWNjdW11bGF0ZSIKICAgICAgIGQ9Im0gMTEyMi45MDAxLDc2NS45MTMwMyBjIC0yMDIuMzA2NjksNC42OTA1IC00MDMuNzQ0MDUsLTEuMTEzODEgLTYwNS45NTQ1NCwzLjM1MzkgLTEwLjg2MzYyLDAuMjQwMDIgLTMuMzYxNDcsLTguNTg2MyAtMjguNTM2OCwtOC41ODYzIgogICAgICAgaWQ9InBhdGgzOTg0IgogICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY3NjIiAvPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJjb2xvcjojMDAwMDAwO2ZpbGw6bm9uZTtzdHJva2U6IzMzMzM2NjtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLW9wYWNpdHk6MTtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLWRhc2hvZmZzZXQ6MDttYXJrZXI6bm9uZTt2aXNpYmlsaXR5OnZpc2libGU7ZGlzcGxheTppbmxpbmU7b3ZlcmZsb3c6dmlzaWJsZTtlbmFibGUtYmFja2dyb3VuZDphY2N1bXVsYXRlIgogICAgICAgZD0ibSA4NjAuMDA4MDUsNzM3LjA2NjUxIGMgMCwwIC05Ny40NDc1LDAuODU4MDYgLTE0Ny41Njg5MiwwLjg1ODA2IC01LjI2ODYxLDAgLTQuNTE1NDYsLTguMzI5ODYgLTcuMzAwODksLTguMzI5ODYgLTMuOTc0MzUsMCAtOC42MjkyNSwwLjAyMDEgLTEwLjUwOTQ4LDAuMDM1OSAtMi4zMzQ3NywwLjAxOTcgLTEuODEwOTQsOC4zNjU5NyAtNC4xNDU4LDguMzY2OTIgLTQ2LjE2ODk5LDAuMDE4OCAtMTY3LjQwNzY3LC0xLjMwNzk5IC0xNzUuMDUyNjMsLTEuMzA3OTkgLTQuNDI5NTUsMCAtOC41NzYyNywtNi40Mzk3MiAtMTMuMTMxOTgsLTYuNDM5NzIgLTEuMzYxMTUsMCAtNi4yMzg3MywwIC0xNC4zOTQ2NywwIgogICAgICAgaWQ9InBhdGgzOTg2IgogICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY3Nzc3Nzc2MiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImNvbG9yOiMwMDAwMDA7ZmlsbDpub25lO3N0cm9rZTojMzMzMzY2O3N0cm9rZS13aWR0aDoxcHg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW1pdGVybGltaXQ6NDtzdHJva2Utb3BhY2l0eToxO3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2UtZGFzaG9mZnNldDowO21hcmtlcjpub25lO3Zpc2liaWxpdHk6dmlzaWJsZTtkaXNwbGF5OmlubGluZTtvdmVyZmxvdzp2aXNpYmxlO2VuYWJsZS1iYWNrZ3JvdW5kOmFjY3VtdWxhdGUiCiAgICAgICBkPSJNIDY3NS4wMDcwMyw4MzEuMTc0MDIgNjc0LjM5NzI1LDMwOS40MDI5OSIKICAgICAgIGlkPSJwYXRoMzk4OCIKICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImNvbG9yOiMwMDAwMDA7ZmlsbDpub25lO3N0cm9rZTojMzMzMzY2O3N0cm9rZS13aWR0aDoxcHg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW1pdGVybGltaXQ6NDtzdHJva2Utb3BhY2l0eToxO3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2UtZGFzaG9mZnNldDowO21hcmtlcjpub25lO3Zpc2liaWxpdHk6dmlzaWJsZTtkaXNwbGF5OmlubGluZTtvdmVyZmxvdzp2aXNpYmxlO2VuYWJsZS1iYWNrZ3JvdW5kOmFjY3VtdWxhdGUiCiAgICAgICBkPSJtIDc5OS40MDE1NywzMTMuMDYxNjUgMS4yMTk1NSw0OTUuODY2NTMiCiAgICAgICBpZD0icGF0aDM5OTAiCiAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIiAvPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJjb2xvcjojMDAwMDAwO2ZpbGw6bm9uZTtzdHJva2U6IzMzMzM2NjtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLW9wYWNpdHk6MTtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLWRhc2hvZmZzZXQ6MDttYXJrZXI6bm9uZTt2aXNpYmlsaXR5OnZpc2libGU7ZGlzcGxheTppbmxpbmU7b3ZlcmZsb3c6dmlzaWJsZTtlbmFibGUtYmFja2dyb3VuZDphY2N1bXVsYXRlIgogICAgICAgZD0ibSA3MzYuNTk0NTIsMzEyLjQ1MTg4IC0xLjIxOTU1LDcxNi40ODgyMiIKICAgICAgIGlkPSJwYXRoMzk5MiIKICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImNvbG9yOiMwMDAwMDA7ZmlsbDpub25lO3N0cm9rZTojMzMzMzY2O3N0cm9rZS13aWR0aDoxcHg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW1pdGVybGltaXQ6NDtzdHJva2Utb3BhY2l0eToxO3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2UtZGFzaG9mZnNldDowO21hcmtlcjpub25lO3Zpc2liaWxpdHk6dmlzaWJsZTtkaXNwbGF5OmlubGluZTtvdmVyZmxvdzp2aXNpYmxlO2VuYWJsZS1iYWNrZ3JvdW5kOmFjY3VtdWxhdGUiCiAgICAgICBkPSJtIDUzMC4wMzA5NCw2NDMuNDU4NTkgMzkyLjM3MTU5LC0zLjAxODI1IgogICAgICAgaWQ9InBhdGg0MDQ4IgogICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIgLz4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iY29sb3I6IzAwMDAwMDtmaWxsOm5vbmU7c3Ryb2tlOiMzMzMzNjY7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1vcGFjaXR5OjE7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1kYXNob2Zmc2V0OjA7bWFya2VyOm5vbmU7dmlzaWJpbGl0eTp2aXNpYmxlO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7ZW5hYmxlLWJhY2tncm91bmQ6YWNjdW11bGF0ZSIKICAgICAgIGQ9Im0gODU5LjQ1MDYsMzE0LjkwMTI4IDEuMjkzNTQsNTA3Ljk4MDU4IgogICAgICAgaWQ9InBhdGg0MDUwIgogICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIgLz4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iY29sb3I6IzAwMDAwMDtmaWxsOm5vbmU7c3Ryb2tlOiMzMzMzNjY7c3Ryb2tlLXdpZHRoOjAuOTk5OTk5OTRweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1vcGFjaXR5OjE7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1kYXNob2Zmc2V0OjA7bWFya2VyOm5vbmU7dmlzaWJpbGl0eTp2aXNpYmxlO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7ZW5hYmxlLWJhY2tncm91bmQ6YWNjdW11bGF0ZSIKICAgICAgIGQ9Im0gOTIxLjU0MDE3LDMxMC41ODk0OSAxLjcyNDcxLDUzMS43NTIyNyIKICAgICAgIGlkPSJwYXRoNDA1MiIKICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImNvbG9yOiMwMDAwMDA7ZmlsbDpub25lO3N0cm9rZTojMzMzMzY2O3N0cm9rZS13aWR0aDoxcHg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW1pdGVybGltaXQ6NDtzdHJva2Utb3BhY2l0eToxO3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2UtZGFzaG9mZnNldDowO21hcmtlcjpub25lO3Zpc2liaWxpdHk6dmlzaWJsZTtkaXNwbGF5OmlubGluZTtvdmVyZmxvdzp2aXNpYmxlO2VuYWJsZS1iYWNrZ3JvdW5kOmFjY3VtdWxhdGUiCiAgICAgICBkPSJtIDczNi4yODk2Myw0NTMuMzEwNCAxODUuNjc3MTUsLTAuMzA0ODkiCiAgICAgICBpZD0icGF0aDQxODciCiAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIiAvPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJjb2xvcjojMDAwMDAwO2ZpbGw6bm9uZTtzdHJva2U6IzMzMzM2NjtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLW9wYWNpdHk6MTtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLWRhc2hvZmZzZXQ6MDttYXJrZXI6bm9uZTt2aXNpYmlsaXR5OnZpc2libGU7ZGlzcGxheTppbmxpbmU7b3ZlcmZsb3c6dmlzaWJsZTtlbmFibGUtYmFja2dyb3VuZDphY2N1bXVsYXRlIgogICAgICAgZD0ibSAxMDYwLjgxMDUsNTE0Ljk2NzY3IGMgMCwwIC0zNjMuMjgxMjYsLTUuNjI2MTggLTU0NC42NTA0MiwyLjUyMTc4IC00LjE3Nzc2LDAuMTg3NjkgLTEyLjUwMDQ0LDEuMDY3MTEgLTEyLjUwMDQ0LDEuMDY3MTEgLTEuNTcwOTUsMC4xMzQxIC0yLjAwMDkzLC0yLjMyNDk1IC0yLjU5MTU1LC0zLjUwNjIzIC0wLjA5NjcsLTAuMTkzNDMgLTcuMDYwODEsLTEuOTMzNCAtNy42MjIyMSwtMS4zNzE5OSAtMi44OTMxNCwyLjg5MzE0IC03LjYzMTY3LDQuMjQ4NjkgLTEyLjE5NTU1LDQuMTE2IEwgMzY5LjIwMTcsNTE0LjUzNjUiCiAgICAgICBpZD0icGF0aDQyNjEiCiAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgc29kaXBvZGk6bm9kZXR5cGVzPSJjc3Nzc3NjIiAvPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJjb2xvcjojMDAwMDAwO2ZpbGw6bm9uZTtzdHJva2U6IzMzMzM2NjtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLW9wYWNpdHk6MTtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLWRhc2hvZmZzZXQ6MDttYXJrZXI6bm9uZTt2aXNpYmlsaXR5OnZpc2libGU7ZGlzcGxheTppbmxpbmU7b3ZlcmZsb3c6dmlzaWJsZTtlbmFibGUtYmFja2dyb3VuZDphY2N1bXVsYXRlIgogICAgICAgZD0ibSAzOTkuODE1MzEsNDc5LjYxMTEyIDExLjY0MTgsNS42MDUzIGMgMi45ODQxMiwxLjQzNjc5IDYuNTI4NzgsLTAuNDc3MTIgOS45MTcwOCwtMC40MzExOCBsIDEyNy4xOTczOSwxLjcyNDcxIgogICAgICAgaWQ9InBhdGg0MjYzIgogICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY3NzYyIgLz4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iY29sb3I6IzAwMDAwMDtmaWxsOm5vbmU7c3Ryb2tlOiMzMzMzNjY7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1vcGFjaXR5OjE7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1kYXNob2Zmc2V0OjA7bWFya2VyOm5vbmU7dmlzaWJpbGl0eTp2aXNpYmxlO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7ZW5hYmxlLWJhY2tncm91bmQ6YWNjdW11bGF0ZSIKICAgICAgIGQ9Ik0gNTE5LjI1MTUxLDUxNy4xMjM1NyA1MTguODIwMzIsMzA4LjQzMzYyIgogICAgICAgaWQ9InBhdGg0MjY1IgogICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIgLz4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iY29sb3I6IzAwMDAwMDtmaWxsOm5vbmU7c3Ryb2tlOiMzMzMzNjY7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1vcGFjaXR5OjE7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1kYXNob2Zmc2V0OjA7bWFya2VyOm5vbmU7dmlzaWJpbGl0eTp2aXNpYmxlO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7ZW5hYmxlLWJhY2tncm91bmQ6YWNjdW11bGF0ZSIKICAgICAgIGQ9Im0gNDMyLjkyNTQ5LDM4OS43MTQ5OCBjIDExLjA0NDk2LDAgMzUuNTMzMDcsMC42MTkyNyA0Mi41Nzk3OCwtMS4wMDM5NyA4LjQwNTIyLC0xLjkzNjE4IDcuMDY2LC02Ljk1Mzc4IDE0LjE5NzEyLC02Ljk1Mzc4IDcuODA5NSwwIDYuNTQyOTEsOC4wNjIzNyAyMC4xNDE3LDguMDYyMzcgMTMuOTkwNjgsMCA0NC45NzY4OSwwLjM3ODg2IDYzLjkzOTkyLDAuMzc4ODYgMTIuMDgzOTUsMCA4Mi4wMDI2NiwwLjMwNDg5IDkzLjYwMDgxLDAuMzA0ODkgOC43NjA0NywwIDEzLjE1OTcsLTIuMjg4MjcgMjEuMzQyMTksLTcuMDEyNDMgNy4xOTUxNSwtNC4xNTQxMyAyLjA1NDU5LC05LjQ5MTM3IDIwLjQyNzU0LC04Ljg0MTc3IDIzLjE0NTQsMC44MTgzMyAxMi42NDMzNCwxNC4wMjQ4NyAzMi4zMTgxOSwxNC4wMjQ4NyAyNS4zNTk1NCwwIDEzMC45OTkwMiwwIDE1MC45MTk4NSwwIDE0LjMzMjQ0LDAgLTQuMTE5MTEsLTEzLjExMDIxIDI5LjI2OTMsLTEzLjQxNTEiCiAgICAgICBpZD0icGF0aDQyNjkiCiAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgc29kaXBvZGk6bm9kZXR5cGVzPSJjc3Nzc3Nzc3NzYyIgLz4KICAgIDx0ZXh0CiAgICAgICB4bWw6c3BhY2U9InByZXNlcnZlIgogICAgICAgc3R5bGU9ImZvbnQtc2l6ZTo5LjY1ODM3NzY1cHg7Zm9udC1zdHlsZTpub3JtYWw7Zm9udC12YXJpYW50Om5vcm1hbDtmb250LXdlaWdodDpub3JtYWw7Zm9udC1zdHJldGNoOm5vcm1hbDtsaW5lLWhlaWdodDoxMjUlO2xldHRlci1zcGFjaW5nOjBweDt3b3JkLXNwYWNpbmc6MHB4O2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZTtmb250LWZhbWlseTpWZXJkYW5hOy1pbmtzY2FwZS1mb250LXNwZWNpZmljYXRpb246VmVyZGFuYSIKICAgICAgIHg9IjU4OC42Nzk1NyIKICAgICAgIHk9IjczNS44MDQ2MyIKICAgICAgIGlkPSJ0ZXh0NDMxMCIKICAgICAgIHNvZGlwb2RpOmxpbmVzcGFjaW5nPSIxMjUlIj48dHNwYW4KICAgICAgICAgc29kaXBvZGk6cm9sZT0ibGluZSIKICAgICAgICAgaWQ9InRzcGFuNDMxMiIKICAgICAgICAgeD0iNTg4LjY3OTU3IgogICAgICAgICB5PSI3MzUuODA0NjMiPkxpbmNvbG48L3RzcGFuPjwvdGV4dD4KICAgIDx0ZXh0CiAgICAgICB4bWw6c3BhY2U9InByZXNlcnZlIgogICAgICAgc3R5bGU9ImZvbnQtc2l6ZTo5LjY1ODM3NzY1cHg7Zm9udC1zdHlsZTpub3JtYWw7Zm9udC12YXJpYW50Om5vcm1hbDtmb250LXdlaWdodDpub3JtYWw7Zm9udC1zdHJldGNoOm5vcm1hbDtsaW5lLWhlaWdodDoxMjUlO2xldHRlci1zcGFjaW5nOjBweDt3b3JkLXNwYWNpbmc6MHB4O2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZTtmb250LWZhbWlseTpWZXJkYW5hOy1pbmtzY2FwZS1mb250LXNwZWNpZmljYXRpb246VmVyZGFuYSIKICAgICAgIHg9IjY4Ni4zOTg1IgogICAgICAgeT0iNzY1LjYyODQyIgogICAgICAgaWQ9InRleHQ0MzEwLTciCiAgICAgICBzb2RpcG9kaTpsaW5lc3BhY2luZz0iMTI1JSI+PHRzcGFuCiAgICAgICAgIHNvZGlwb2RpOnJvbGU9ImxpbmUiCiAgICAgICAgIGlkPSJ0c3BhbjQzMTItNiIKICAgICAgICAgeD0iNjg2LjM5ODUiCiAgICAgICAgIHk9Ijc2NS42Mjg0MiI+SGFycnk8L3RzcGFuPjwvdGV4dD4KICAgIDx0ZXh0CiAgICAgICB4bWw6c3BhY2U9InByZXNlcnZlIgogICAgICAgc3R5bGU9ImZvbnQtc2l6ZTo5LjY1ODM3NzY1cHg7Zm9udC1zdHlsZTpub3JtYWw7Zm9udC12YXJpYW50Om5vcm1hbDtmb250LXdlaWdodDpub3JtYWw7Zm9udC1zdHJldGNoOm5vcm1hbDtsaW5lLWhlaWdodDoxMjUlO2xldHRlci1zcGFjaW5nOjBweDt3b3JkLXNwYWNpbmc6MHB4O2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZTtmb250LWZhbWlseTpWZXJkYW5hOy1pbmtzY2FwZS1mb250LXNwZWNpZmljYXRpb246VmVyZGFuYSIKICAgICAgIHg9IjcwOS44NzE4MyIKICAgICAgIHk9Ii04MDIuMzc3MzgiCiAgICAgICBpZD0idGV4dDQzMTAtNy0xIgogICAgICAgc29kaXBvZGk6bGluZXNwYWNpbmc9IjEyNSUiCiAgICAgICB0cmFuc2Zvcm09Im1hdHJpeCgwLDEsLTEsMCwwLDApIj48dHNwYW4KICAgICAgICAgc29kaXBvZGk6cm9sZT0ibGluZSIKICAgICAgICAgaWQ9InRzcGFuNDMxMi02LTgiCiAgICAgICAgIHg9IjcwOS44NzE4MyIKICAgICAgICAgeT0iLTgwMi4zNzczOCI+V29vZGxhd248L3RzcGFuPjwvdGV4dD4KICAgIDx0ZXh0CiAgICAgICB4bWw6c3BhY2U9InByZXNlcnZlIgogICAgICAgc3R5bGU9ImZvbnQtc2l6ZTo5LjY1ODM3NzY1cHg7Zm9udC1zdHlsZTpub3JtYWw7Zm9udC12YXJpYW50Om5vcm1hbDtmb250LXdlaWdodDpub3JtYWw7Zm9udC1zdHJldGNoOm5vcm1hbDtsaW5lLWhlaWdodDoxMjUlO2xldHRlci1zcGFjaW5nOjBweDt3b3JkLXNwYWNpbmc6MHB4O2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZTtmb250LWZhbWlseTpWZXJkYW5hOy1pbmtzY2FwZS1mb250LXNwZWNpZmljYXRpb246VmVyZGFuYSIKICAgICAgIHg9IjU2Mi4xMTkyNiIKICAgICAgIHk9Ii03NzEuOTY4MTQiCiAgICAgICBpZD0idGV4dDQzMTAtNy0xLTkiCiAgICAgICBzb2RpcG9kaTpsaW5lc3BhY2luZz0iMTI1JSIKICAgICAgIHRyYW5zZm9ybT0ibWF0cml4KDAsMSwtMSwwLDAsMCkiPjx0c3BhbgogICAgICAgICBzb2RpcG9kaTpyb2xlPSJsaW5lIgogICAgICAgICBpZD0idHNwYW40MzEyLTYtOC0yIgogICAgICAgICB4PSI1NjIuMTE5MjYiCiAgICAgICAgIHk9Ii03NzEuOTY4MTQiPkVkZ2Vtb29yPC90c3Bhbj48L3RleHQ+CiAgICA8dGV4dAogICAgICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIKICAgICAgIHN0eWxlPSJmb250LXNpemU6OS42NTgzNzc2NXB4O2ZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7bGluZS1oZWlnaHQ6MTI1JTtsZXR0ZXItc3BhY2luZzowcHg7d29yZC1zcGFjaW5nOjBweDtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7Zm9udC1mYW1pbHk6VmVyZGFuYTstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOlZlcmRhbmEiCiAgICAgICB4PSI1OTguMzA0ODciCiAgICAgICB5PSItNzM4LjM2NjQ2IgogICAgICAgaWQ9InRleHQ0MzEwLTctMS05LTciCiAgICAgICBzb2RpcG9kaTpsaW5lc3BhY2luZz0iMTI1JSIKICAgICAgIHRyYW5zZm9ybT0ibWF0cml4KDAsMSwtMSwwLDAsMCkiPjx0c3BhbgogICAgICAgICBzb2RpcG9kaTpyb2xlPSJsaW5lIgogICAgICAgICBpZD0idHNwYW40MzEyLTYtOC0yLTkiCiAgICAgICAgIHg9IjU5OC4zMDQ4NyIKICAgICAgICAgeT0iLTczOC4zNjY0NiI+T2xpdmVyPC90c3Bhbj48L3RleHQ+CiAgICA8dGV4dAogICAgICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIKICAgICAgIHN0eWxlPSJmb250LXNpemU6OS42NTgzNzc2NXB4O2ZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7bGluZS1oZWlnaHQ6MTI1JTtsZXR0ZXItc3BhY2luZzowcHg7d29yZC1zcGFjaW5nOjBweDtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7Zm9udC1mYW1pbHk6VmVyZGFuYTstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOlZlcmRhbmEiCiAgICAgICB4PSI1OTIuMTIyODYiCiAgICAgICB5PSItNjc3LjIwMzk4IgogICAgICAgaWQ9InRleHQ0MzEwLTctMS05LTctNSIKICAgICAgIHNvZGlwb2RpOmxpbmVzcGFjaW5nPSIxMjUlIgogICAgICAgdHJhbnNmb3JtPSJtYXRyaXgoMCwxLC0xLDAsMCwwKSI+PHRzcGFuCiAgICAgICAgIHNvZGlwb2RpOnJvbGU9ImxpbmUiCiAgICAgICAgIGlkPSJ0c3BhbjQzMTItNi04LTItOS00IgogICAgICAgICB4PSI1OTIuMTIyODYiCiAgICAgICAgIHk9Ii02NzcuMjAzOTgiPkhpbGxzaWRlPC90c3Bhbj48L3RleHQ+CiAgICA8dGV4dAogICAgICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIKICAgICAgIHN0eWxlPSJmb250LXNpemU6OS42NTgzNzc2NXB4O2ZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7bGluZS1oZWlnaHQ6MTI1JTtsZXR0ZXItc3BhY2luZzowcHg7d29yZC1zcGFjaW5nOjBweDtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7Zm9udC1mYW1pbHk6VmVyZGFuYTstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOlZlcmRhbmEiCiAgICAgICB4PSI1OTcuMzI3MDkiCiAgICAgICB5PSItODYyLjYxNDA3IgogICAgICAgaWQ9InRleHQ0MzEwLTctMS05LTctNS0zIgogICAgICAgc29kaXBvZGk6bGluZXNwYWNpbmc9IjEyNSUiCiAgICAgICB0cmFuc2Zvcm09Im1hdHJpeCgwLDEsLTEsMCwwLDApIj48dHNwYW4KICAgICAgICAgc29kaXBvZGk6cm9sZT0ibGluZSIKICAgICAgICAgaWQ9InRzcGFuNDMxMi02LTgtMi05LTQtMSIKICAgICAgICAgeD0iNTk3LjMyNzA5IgogICAgICAgICB5PSItODYyLjYxNDA3Ij5Sb2NrPC90c3Bhbj48L3RleHQ+CiAgICA8dGV4dAogICAgICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIKICAgICAgIHN0eWxlPSJmb250LXNpemU6OS42NTgzNzc2NXB4O2ZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7bGluZS1oZWlnaHQ6MTI1JTtsZXR0ZXItc3BhY2luZzowcHg7d29yZC1zcGFjaW5nOjBweDtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7Zm9udC1mYW1pbHk6VmVyZGFuYTstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOlZlcmRhbmEiCiAgICAgICB4PSI1ODcuMzcwMTgiCiAgICAgICB5PSItOTI2LjEzNjYiCiAgICAgICBpZD0idGV4dDQzMTAtNy0xLTktNy01LTMtMiIKICAgICAgIHNvZGlwb2RpOmxpbmVzcGFjaW5nPSIxMjUlIgogICAgICAgdHJhbnNmb3JtPSJtYXRyaXgoMCwxLC0xLDAsMCwwKSI+PHRzcGFuCiAgICAgICAgIHNvZGlwb2RpOnJvbGU9ImxpbmUiCiAgICAgICAgIGlkPSJ0c3BhbjQzMTItNi04LTItOS00LTEtMyIKICAgICAgICAgeD0iNTg3LjM3MDE4IgogICAgICAgICB5PSItOTI2LjEzNjYiPldlYmI8L3RzcGFuPjwvdGV4dD4KICAgIDx0ZXh0CiAgICAgICB4bWw6c3BhY2U9InByZXNlcnZlIgogICAgICAgc3R5bGU9ImZvbnQtc2l6ZTo5LjY1ODM3NzY1cHg7Zm9udC1zdHlsZTpub3JtYWw7Zm9udC12YXJpYW50Om5vcm1hbDtmb250LXdlaWdodDpub3JtYWw7Zm9udC1zdHJldGNoOm5vcm1hbDtsaW5lLWhlaWdodDoxMjUlO2xldHRlci1zcGFjaW5nOjBweDt3b3JkLXNwYWNpbmc6MHB4O2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZTtmb250LWZhbWlseTpWZXJkYW5hOy1pbmtzY2FwZS1mb250LXNwZWNpZmljYXRpb246VmVyZGFuYSIKICAgICAgIHg9Ijg3MS4xNjEwMSIKICAgICAgIHk9IjYzNy41NzUyIgogICAgICAgaWQ9InRleHQ0NDY1IgogICAgICAgc29kaXBvZGk6bGluZXNwYWNpbmc9IjEyNSUiPjx0c3BhbgogICAgICAgICBzb2RpcG9kaTpyb2xlPSJsaW5lIgogICAgICAgICBpZD0idHNwYW40NDY3IgogICAgICAgICB4PSI4NzEuMTYxMDEiCiAgICAgICAgIHk9IjYzNy41NzUyIj5DZW50cmFsPC90c3Bhbj48L3RleHQ+CiAgICA8dGV4dAogICAgICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIKICAgICAgIHN0eWxlPSJmb250LXNpemU6OS42NTgzNzc2NXB4O2ZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7bGluZS1oZWlnaHQ6MTI1JTtsZXR0ZXItc3BhY2luZzowcHg7d29yZC1zcGFjaW5nOjBweDtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7Zm9udC1mYW1pbHk6VmVyZGFuYTstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOlZlcmRhbmEiCiAgICAgICB4PSI4NzMuODMyMjgiCiAgICAgICB5PSI1NzcuMDMyNDciCiAgICAgICBpZD0idGV4dDQ0NjUtMyIKICAgICAgIHNvZGlwb2RpOmxpbmVzcGFjaW5nPSIxMjUlIj48dHNwYW4KICAgICAgICAgc29kaXBvZGk6cm9sZT0ibGluZSIKICAgICAgICAgaWQ9InRzcGFuNDQ2Ny00IgogICAgICAgICB4PSI4NzMuODMyMjgiCiAgICAgICAgIHk9IjU3Ny4wMzI0NyI+MTN0aDwvdHNwYW4+PC90ZXh0PgogICAgPHRleHQKICAgICAgIHNvZGlwb2RpOmxpbmVzcGFjaW5nPSIxMjUlIgogICAgICAgaWQ9InRleHQ0NDkwIgogICAgICAgeT0iNTEwLjI2MTgxIgogICAgICAgeD0iODc1Ljk2NjQ5IgogICAgICAgc3R5bGU9ImZvbnQtc2l6ZTo5LjY1ODM3NzY1cHg7Zm9udC1zdHlsZTpub3JtYWw7Zm9udC12YXJpYW50Om5vcm1hbDtmb250LXdlaWdodDpub3JtYWw7Zm9udC1zdHJldGNoOm5vcm1hbDtsaW5lLWhlaWdodDoxMjUlO2xldHRlci1zcGFjaW5nOjBweDt3b3JkLXNwYWNpbmc6MHB4O2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZTtmb250LWZhbWlseTpWZXJkYW5hOy1pbmtzY2FwZS1mb250LXNwZWNpZmljYXRpb246VmVyZGFuYSIKICAgICAgIHhtbDpzcGFjZT0icHJlc2VydmUiPjx0c3BhbgogICAgICAgICB5PSI1MTAuMjYxODEiCiAgICAgICAgIHg9Ijg3NS45NjY0OSIKICAgICAgICAgaWQ9InRzcGFuNDQ5MiIKICAgICAgICAgc29kaXBvZGk6cm9sZT0ibGluZSI+MjFzdDwvdHNwYW4+PC90ZXh0PgogICAgPHRleHQKICAgICAgIHhtbDpzcGFjZT0icHJlc2VydmUiCiAgICAgICBzdHlsZT0iZm9udC1zaXplOjkuNjU4Mzc3NjVweDtmb250LXN0eWxlOm5vcm1hbDtmb250LXZhcmlhbnQ6bm9ybWFsO2ZvbnQtd2VpZ2h0Om5vcm1hbDtmb250LXN0cmV0Y2g6bm9ybWFsO2xpbmUtaGVpZ2h0OjEyNSU7bGV0dGVyLXNwYWNpbmc6MHB4O3dvcmQtc3BhY2luZzowcHg7ZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lO2ZvbnQtZmFtaWx5OlZlcmRhbmE7LWlua3NjYXBlLWZvbnQtc3BlY2lmaWNhdGlvbjpWZXJkYW5hIgogICAgICAgeD0iODgxLjMxNjU5IgogICAgICAgeT0iNDUwLjE5ODc2IgogICAgICAgaWQ9InRleHQ0NDk0IgogICAgICAgc29kaXBvZGk6bGluZXNwYWNpbmc9IjEyNSUiPjx0c3BhbgogICAgICAgICBzb2RpcG9kaTpyb2xlPSJsaW5lIgogICAgICAgICBpZD0idHNwYW40NDk2IgogICAgICAgICB4PSI4ODEuMzE2NTkiCiAgICAgICAgIHk9IjQ1MC4xOTg3NiI+Mjl0aDwvdHNwYW4+PC90ZXh0PgogICAgPHRleHQKICAgICAgIHhtbDpzcGFjZT0icHJlc2VydmUiCiAgICAgICBzdHlsZT0iZm9udC1zaXplOjkuNjU4Mzc3NjVweDtmb250LXN0eWxlOm5vcm1hbDtmb250LXZhcmlhbnQ6bm9ybWFsO2ZvbnQtd2VpZ2h0Om5vcm1hbDtmb250LXN0cmV0Y2g6bm9ybWFsO2xpbmUtaGVpZ2h0OjEyNSU7bGV0dGVyLXNwYWNpbmc6MHB4O3dvcmQtc3BhY2luZzowcHg7ZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lO2ZvbnQtZmFtaWx5OlZlcmRhbmE7LWlua3NjYXBlLWZvbnQtc3BlY2lmaWNhdGlvbjpWZXJkYW5hIgogICAgICAgeD0iNjE1Ljc5MjQ4IgogICAgICAgeT0iMzg3Ljc0NzE2IgogICAgICAgaWQ9InRleHQ0NDY1LTMtMSIKICAgICAgIHNvZGlwb2RpOmxpbmVzcGFjaW5nPSIxMjUlIj48dHNwYW4KICAgICAgICAgc29kaXBvZGk6cm9sZT0ibGluZSIKICAgICAgICAgaWQ9InRzcGFuNDQ2Ny00LTEiCiAgICAgICAgIHg9IjYxNS43OTI0OCIKICAgICAgICAgeT0iMzg3Ljc0NzE2Ij4zN3RoPC90c3Bhbj48L3RleHQ+CiAgICA8dGV4dAogICAgICAgc29kaXBvZGk6bGluZXNwYWNpbmc9IjEyNSUiCiAgICAgICBpZD0idGV4dDQ1MTkiCiAgICAgICB5PSI0ODEuNjUyODYiCiAgICAgICB4PSI0ODQuNjkwMzciCiAgICAgICBzdHlsZT0iZm9udC1zaXplOjkuNjU4Mzc3NjVweDtmb250LXN0eWxlOm5vcm1hbDtmb250LXZhcmlhbnQ6bm9ybWFsO2ZvbnQtd2VpZ2h0Om5vcm1hbDtmb250LXN0cmV0Y2g6bm9ybWFsO2xpbmUtaGVpZ2h0OjEyNSU7bGV0dGVyLXNwYWNpbmc6MHB4O3dvcmQtc3BhY2luZzowcHg7ZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lO2ZvbnQtZmFtaWx5OlZlcmRhbmE7LWlua3NjYXBlLWZvbnQtc3BlY2lmaWNhdGlvbjpWZXJkYW5hIgogICAgICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuCiAgICAgICAgIHk9IjQ4MS42NTI4NiIKICAgICAgICAgeD0iNDg0LjY5MDM3IgogICAgICAgICBpZD0idHNwYW40NTIxIgogICAgICAgICBzb2RpcG9kaTpyb2xlPSJsaW5lIj4yNXRoPC90c3Bhbj48L3RleHQ+CiAgICA8dGV4dAogICAgICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIKICAgICAgIHN0eWxlPSJmb250LXNpemU6OS42NTgzNzc2NXB4O2ZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7bGluZS1oZWlnaHQ6MTI1JTtsZXR0ZXItc3BhY2luZzowcHg7d29yZC1zcGFjaW5nOjBweDtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7Zm9udC1mYW1pbHk6VmVyZGFuYTstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOlZlcmRhbmEiCiAgICAgICB4PSI1NjMuMDQ2NzUiCiAgICAgICB5PSI1MTMuMzYxMzMiCiAgICAgICBpZD0idGV4dDQ1MjMiCiAgICAgICBzb2RpcG9kaTpsaW5lc3BhY2luZz0iMTI1JSI+PHRzcGFuCiAgICAgICAgIHNvZGlwb2RpOnJvbGU9ImxpbmUiCiAgICAgICAgIGlkPSJ0c3BhbjQ1MjUiCiAgICAgICAgIHg9IjU2My4wNDY3NSIKICAgICAgICAgeT0iNTEzLjM2MTMzIj4yMXN0PC90c3Bhbj48L3RleHQ+CiAgICA8dGV4dAogICAgICAgc29kaXBvZGk6bGluZXNwYWNpbmc9IjEyNSUiCiAgICAgICBpZD0idGV4dDQ1MjciCiAgICAgICB5PSI1NzcuODk0ODQiCiAgICAgICB4PSI1NjUuOTcxNSIKICAgICAgIHN0eWxlPSJmb250LXNpemU6OS42NTgzNzc2NXB4O2ZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7bGluZS1oZWlnaHQ6MTI1JTtsZXR0ZXItc3BhY2luZzowcHg7d29yZC1zcGFjaW5nOjBweDtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7Zm9udC1mYW1pbHk6VmVyZGFuYTstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOlZlcmRhbmEiCiAgICAgICB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4KICAgICAgICAgeT0iNTc3Ljg5NDg0IgogICAgICAgICB4PSI1NjUuOTcxNSIKICAgICAgICAgaWQ9InRzcGFuNDUyOSIKICAgICAgICAgc29kaXBvZGk6cm9sZT0ibGluZSI+MTN0aDwvdHNwYW4+PC90ZXh0PgogICAgPHRleHQKICAgICAgIHRyYW5zZm9ybT0ibWF0cml4KDAsMSwtMSwwLDAsMCkiCiAgICAgICBzb2RpcG9kaTpsaW5lc3BhY2luZz0iMTI1JSIKICAgICAgIGlkPSJ0ZXh0NDUzMSIKICAgICAgIHk9Ii00NjAuNzMzMTIiCiAgICAgICB4PSI0MzMuNTgwNzUiCiAgICAgICBzdHlsZT0iZm9udC1zaXplOjkuNjU4Mzc3NjVweDtmb250LXN0eWxlOm5vcm1hbDtmb250LXZhcmlhbnQ6bm9ybWFsO2ZvbnQtd2VpZ2h0Om5vcm1hbDtmb250LXN0cmV0Y2g6bm9ybWFsO2xpbmUtaGVpZ2h0OjEyNSU7bGV0dGVyLXNwYWNpbmc6MHB4O3dvcmQtc3BhY2luZzowcHg7ZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lO2ZvbnQtZmFtaWx5OlZlcmRhbmE7LWlua3NjYXBlLWZvbnQtc3BlY2lmaWNhdGlvbjpWZXJkYW5hIgogICAgICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuCiAgICAgICAgIHk9Ii00NjAuNzMzMTIiCiAgICAgICAgIHg9IjQzMy41ODA3NSIKICAgICAgICAgaWQ9InRzcGFuNDUzMyIKICAgICAgICAgc29kaXBvZGk6cm9sZT0ibGluZSI+QW1pZG9uPC90c3Bhbj48L3RleHQ+CiAgICA8dGV4dAogICAgICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIKICAgICAgIHN0eWxlPSJmb250LXNpemU6OS42NTgzNzc2NXB4O2ZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7bGluZS1oZWlnaHQ6MTI1JTtsZXR0ZXItc3BhY2luZzowcHg7d29yZC1zcGFjaW5nOjBweDtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7Zm9udC1mYW1pbHk6VmVyZGFuYTstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOlZlcmRhbmEiCiAgICAgICB4PSI0MDUuNTMwOTgiCiAgICAgICB5PSItNTIzLjU0MDE2IgogICAgICAgaWQ9InRleHQ0NTM1IgogICAgICAgc29kaXBvZGk6bGluZXNwYWNpbmc9IjEyNSUiCiAgICAgICB0cmFuc2Zvcm09Im1hdHJpeCgwLDEsLTEsMCwwLDApIj48dHNwYW4KICAgICAgICAgc29kaXBvZGk6cm9sZT0ibGluZSIKICAgICAgICAgaWQ9InRzcGFuNDUzNyIKICAgICAgICAgeD0iNDA1LjUzMDk4IgogICAgICAgICB5PSItNTIzLjU0MDE2Ij5BcmthbnNhczwvdHNwYW4+PC90ZXh0PgogICAgPHRleHQKICAgICAgIHRyYW5zZm9ybT0ibWF0cml4KDAsMSwtMSwwLDAsMCkiCiAgICAgICBzb2RpcG9kaTpsaW5lc3BhY2luZz0iMTI1JSIKICAgICAgIGlkPSJ0ZXh0NDUzOSIKICAgICAgIHk9Ii0zNzIuNTg1OTQiCiAgICAgICB4PSI3NDUuNDg0NjIiCiAgICAgICBzdHlsZT0iZm9udC1zaXplOjkuNjU4Mzc3NjVweDtmb250LXN0eWxlOm5vcm1hbDtmb250LXZhcmlhbnQ6bm9ybWFsO2ZvbnQtd2VpZ2h0Om5vcm1hbDtmb250LXN0cmV0Y2g6bm9ybWFsO2xpbmUtaGVpZ2h0OjEyNSU7bGV0dGVyLXNwYWNpbmc6MHB4O3dvcmQtc3BhY2luZzowcHg7ZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lO2ZvbnQtZmFtaWx5OlZlcmRhbmE7LWlua3NjYXBlLWZvbnQtc3BlY2lmaWNhdGlvbjpWZXJkYW5hIgogICAgICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuCiAgICAgICAgIHk9Ii0zNzIuNTg1OTQiCiAgICAgICAgIHg9Ijc0NS40ODQ2MiIKICAgICAgICAgaWQ9InRzcGFuNDU0MSIKICAgICAgICAgc29kaXBvZGk6cm9sZT0ibGluZSI+V2VzdDwvdHNwYW4+PC90ZXh0PgogICAgPHRleHQKICAgICAgIHhtbDpzcGFjZT0icHJlc2VydmUiCiAgICAgICBzdHlsZT0iZm9udC1zaXplOjkuNjU4Mzc3NjVweDtmb250LXN0eWxlOm5vcm1hbDtmb250LXZhcmlhbnQ6bm9ybWFsO2ZvbnQtd2VpZ2h0Om5vcm1hbDtmb250LXN0cmV0Y2g6bm9ybWFsO2xpbmUtaGVpZ2h0OjEyNSU7bGV0dGVyLXNwYWNpbmc6MHB4O3dvcmQtc3BhY2luZzowcHg7ZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lO2ZvbnQtZmFtaWx5OlZlcmRhbmE7LWlua3NjYXBlLWZvbnQtc3BlY2lmaWNhdGlvbjpWZXJkYW5hIgogICAgICAgeD0iNTk2LjcyODMzIgogICAgICAgeT0iLTUzMS4yNTkyOCIKICAgICAgIGlkPSJ0ZXh0NDU0MyIKICAgICAgIHNvZGlwb2RpOmxpbmVzcGFjaW5nPSIxMjUlIgogICAgICAgdHJhbnNmb3JtPSJtYXRyaXgoMCwxLC0xLDAsMCwwKSI+PHRzcGFuCiAgICAgICAgIHNvZGlwb2RpOnJvbGU9ImxpbmUiCiAgICAgICAgIGlkPSJ0c3BhbjQ1NDUiCiAgICAgICAgIHg9IjU5Ni43MjgzMyIKICAgICAgICAgeT0iLTUzMS4yNTkyOCI+V2FjbzwvdHNwYW4+PC90ZXh0PgogICAgPHRleHQKICAgICAgIHRyYW5zZm9ybT0ibWF0cml4KDAsMSwtMSwwLDAsMCkiCiAgICAgICBzb2RpcG9kaTpsaW5lc3BhY2luZz0iMTI1JSIKICAgICAgIGlkPSJ0ZXh0NDU1NSIKICAgICAgIHk9Ii0xMjIuNTAyOTUiCiAgICAgICB4PSI1OTUuNDM0ODEiCiAgICAgICBzdHlsZT0iZm9udC1zaXplOjkuNjU4Mzc3NjVweDtmb250LXN0eWxlOm5vcm1hbDtmb250LXZhcmlhbnQ6bm9ybWFsO2ZvbnQtd2VpZ2h0Om5vcm1hbDtmb250LXN0cmV0Y2g6bm9ybWFsO2xpbmUtaGVpZ2h0OjEyNSU7bGV0dGVyLXNwYWNpbmc6MHB4O3dvcmQtc3BhY2luZzowcHg7ZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lO2ZvbnQtZmFtaWx5OlZlcmRhbmE7LWlua3NjYXBlLWZvbnQtc3BlY2lmaWNhdGlvbjpWZXJkYW5hIgogICAgICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuCiAgICAgICAgIHk9Ii0xMjIuNTAyOTUiCiAgICAgICAgIHg9IjU5NS40MzQ4MSIKICAgICAgICAgaWQ9InRzcGFuNDU1NyIKICAgICAgICAgc29kaXBvZGk6cm9sZT0ibGluZSI+TWF6aWU8L3RzcGFuPjwvdGV4dD4KICAgIDx0ZXh0CiAgICAgICB4bWw6c3BhY2U9InByZXNlcnZlIgogICAgICAgc3R5bGU9ImZvbnQtc2l6ZTo5LjY1ODM3NzY1cHg7Zm9udC1zdHlsZTpub3JtYWw7Zm9udC12YXJpYW50Om5vcm1hbDtmb250LXdlaWdodDpub3JtYWw7Zm9udC1zdHJldGNoOm5vcm1hbDtsaW5lLWhlaWdodDoxMjUlO2xldHRlci1zcGFjaW5nOjBweDt3b3JkLXNwYWNpbmc6MHB4O2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZTtmb250LWZhbWlseTpWZXJkYW5hOy1pbmtzY2FwZS1mb250LXNwZWNpZmljYXRpb246VmVyZGFuYSIKICAgICAgIHg9IjY5NS43NzI5NSIKICAgICAgIHk9IjE2Mi4wNjg3NyIKICAgICAgIGlkPSJ0ZXh0NDU1OSIKICAgICAgIHNvZGlwb2RpOmxpbmVzcGFjaW5nPSIxMjUlIgogICAgICAgdHJhbnNmb3JtPSJtYXRyaXgoMC43MDcxMDY3OCwwLjcwNzEwNjc4LC0wLjcwNzEwNjc4LDAuNzA3MTA2NzgsMCwwKSI+PHRzcGFuCiAgICAgICAgIHNvZGlwb2RpOnJvbGU9ImxpbmUiCiAgICAgICAgIGlkPSJ0c3BhbjQ1NjEiCiAgICAgICAgIHg9IjY5NS43NzI5NSIKICAgICAgICAgeT0iMTYyLjA2ODc3Ij5ab288L3RzcGFuPjwvdGV4dD4KICAgIDx0ZXh0CiAgICAgICB4bWw6c3BhY2U9InByZXNlcnZlIgogICAgICAgc3R5bGU9ImZvbnQtc2l6ZTo5LjY1ODM3NzY1cHg7Zm9udC1zdHlsZTpub3JtYWw7Zm9udC12YXJpYW50Om5vcm1hbDtmb250LXdlaWdodDpub3JtYWw7Zm9udC1zdHJldGNoOm5vcm1hbDtsaW5lLWhlaWdodDoxMjUlO2xldHRlci1zcGFjaW5nOjBweDt3b3JkLXNwYWNpbmc6MHB4O2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZTtmb250LWZhbWlseTpWZXJkYW5hOy1pbmtzY2FwZS1mb250LXNwZWNpZmljYXRpb246VmVyZGFuYSIKICAgICAgIHg9IjI0MC41ODk5NyIKICAgICAgIHk9IjU3NC40NDU0MyIKICAgICAgIGlkPSJ0ZXh0NDU2MyIKICAgICAgIHNvZGlwb2RpOmxpbmVzcGFjaW5nPSIxMjUlIj48dHNwYW4KICAgICAgICAgc29kaXBvZGk6cm9sZT0ibGluZSIKICAgICAgICAgaWQ9InRzcGFuNDU2NSIKICAgICAgICAgeD0iMjQwLjU4OTk3IgogICAgICAgICB5PSI1NzQuNDQ1NDMiPjEzdGg8L3RzcGFuPjwvdGV4dD4KICAgIDx0ZXh0CiAgICAgICBzb2RpcG9kaTpsaW5lc3BhY2luZz0iMTI1JSIKICAgICAgIGlkPSJ0ZXh0NDU2NyIKICAgICAgIHk9IjUxMS42MzY2MyIKICAgICAgIHg9IjIwNi4wMzE3NSIKICAgICAgIHN0eWxlPSJmb250LXNpemU6OS42NTgzNzc2NXB4O2ZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7bGluZS1oZWlnaHQ6MTI1JTtsZXR0ZXItc3BhY2luZzowcHg7d29yZC1zcGFjaW5nOjBweDtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7Zm9udC1mYW1pbHk6VmVyZGFuYTstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOlZlcmRhbmEiCiAgICAgICB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4KICAgICAgICAgeT0iNTExLjYzNjYzIgogICAgICAgICB4PSIyMDYuMDMxNzUiCiAgICAgICAgIGlkPSJ0c3BhbjQ1NjkiCiAgICAgICAgIHNvZGlwb2RpOnJvbGU9ImxpbmUiPjIxc3Q8L3RzcGFuPjwvdGV4dD4KICAgIDx0ZXh0CiAgICAgICB4bWw6c3BhY2U9InByZXNlcnZlIgogICAgICAgc3R5bGU9ImZvbnQtc2l6ZTo5LjY1ODM3NzY1cHg7Zm9udC1zdHlsZTpub3JtYWw7Zm9udC12YXJpYW50Om5vcm1hbDtmb250LXdlaWdodDpub3JtYWw7Zm9udC1zdHJldGNoOm5vcm1hbDtsaW5lLWhlaWdodDoxMjUlO2xldHRlci1zcGFjaW5nOjBweDt3b3JkLXNwYWNpbmc6MHB4O2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZTtmb250LWZhbWlseTpWZXJkYW5hOy1pbmtzY2FwZS1mb250LXNwZWNpZmljYXRpb246VmVyZGFuYSIKICAgICAgIHg9IjYyMC40NDMxMiIKICAgICAgIHk9Ii01MDYuNjgyMTkiCiAgICAgICBpZD0idGV4dDQ1NzEiCiAgICAgICBzb2RpcG9kaTpsaW5lc3BhY2luZz0iMTI1JSIKICAgICAgIHRyYW5zZm9ybT0ibWF0cml4KDAsMSwtMSwwLDAsMCkiPjx0c3BhbgogICAgICAgICBzb2RpcG9kaTpyb2xlPSJsaW5lIgogICAgICAgICBpZD0idHNwYW40NTczIgogICAgICAgICB4PSI2MjAuNDQzMTIiCiAgICAgICAgIHk9Ii01MDYuNjgyMTkiPk5pbXM8L3RzcGFuPjwvdGV4dD4KICAgIDx0ZXh0CiAgICAgICBzb2RpcG9kaTpsaW5lc3BhY2luZz0iMTI1JSIKICAgICAgIGlkPSJ0ZXh0NDU4MyIKICAgICAgIHk9IjY5OC44NDAwOSIKICAgICAgIHg9IjM3MC4yMTY4NiIKICAgICAgIHN0eWxlPSJmb250LXNpemU6OS42NTgzNzc2NXB4O2ZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7bGluZS1oZWlnaHQ6MTI1JTtsZXR0ZXItc3BhY2luZzowcHg7d29yZC1zcGFjaW5nOjBweDtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7Zm9udC1mYW1pbHk6VmVyZGFuYTstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOlZlcmRhbmEiCiAgICAgICB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4KICAgICAgICAgeT0iNjk4Ljg0MDA5IgogICAgICAgICB4PSIzNzAuMjE2ODYiCiAgICAgICAgIGlkPSJ0c3BhbjQ1ODUiCiAgICAgICAgIHNvZGlwb2RpOnJvbGU9ImxpbmUiPk1hcGxlPC90c3Bhbj48L3RleHQ+CiAgICA8dGV4dAogICAgICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIKICAgICAgIHN0eWxlPSJmb250LXNpemU6OS42NTgzNzc2NXB4O2ZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7bGluZS1oZWlnaHQ6MTI1JTtsZXR0ZXItc3BhY2luZzowcHg7d29yZC1zcGFjaW5nOjBweDtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7Zm9udC1mYW1pbHk6VmVyZGFuYTstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOlZlcmRhbmEiCiAgICAgICB4PSIzODQuMDg0MiIKICAgICAgIHk9IjY4MC44NTEzOCIKICAgICAgIGlkPSJ0ZXh0NDU5OSIKICAgICAgIHNvZGlwb2RpOmxpbmVzcGFjaW5nPSIxMjUlIj48dHNwYW4KICAgICAgICAgc29kaXBvZGk6cm9sZT0ibGluZSIKICAgICAgICAgaWQ9InRzcGFuNDYwMSIKICAgICAgICAgeD0iMzg0LjA4NDIiCiAgICAgICAgIHk9IjY4MC44NTEzOCI+RG91Z2xhczwvdHNwYW4+PC90ZXh0PgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJjb2xvcjojMDAwMDAwO2ZpbGw6bm9uZTtzdHJva2U6IzMzMzM2NjtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLW9wYWNpdHk6MTtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLWRhc2hvZmZzZXQ6MDttYXJrZXI6bm9uZTt2aXNpYmlsaXR5OnZpc2libGU7ZGlzcGxheTppbmxpbmU7b3ZlcmZsb3c6dmlzaWJsZTtlbmFibGUtYmFja2dyb3VuZDphY2N1bXVsYXRlIgogICAgICAgZD0ibSAzNjcuOTA4MTcsMTAwOS45NTk2IDI2My4wMTgzMywwIgogICAgICAgaWQ9InBhdGg0NjA1IgogICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIgLz4KICAgIDx0ZXh0CiAgICAgICB0cmFuc2Zvcm09Im1hdHJpeCgwLDEsLTEsMCwwLDApIgogICAgICAgc29kaXBvZGk6bGluZXNwYWNpbmc9IjEyNSUiCiAgICAgICBpZD0idGV4dDQ2MDciCiAgICAgICB5PSItNDMzLjEzNzc2IgogICAgICAgeD0iNzM2LjI2NzQ2IgogICAgICAgc3R5bGU9ImZvbnQtc2l6ZTo5LjY1ODM3NzY1cHg7Zm9udC1zdHlsZTpub3JtYWw7Zm9udC12YXJpYW50Om5vcm1hbDtmb250LXdlaWdodDpub3JtYWw7Zm9udC1zdHJldGNoOm5vcm1hbDtsaW5lLWhlaWdodDoxMjUlO2xldHRlci1zcGFjaW5nOjBweDt3b3JkLXNwYWNpbmc6MHB4O2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZTtmb250LWZhbWlseTpWZXJkYW5hOy1pbmtzY2FwZS1mb250LXNwZWNpZmljYXRpb246VmVyZGFuYSIKICAgICAgIHhtbDpzcGFjZT0icHJlc2VydmUiPjx0c3BhbgogICAgICAgICB5PSItNDMzLjEzNzc2IgogICAgICAgICB4PSI3MzYuMjY3NDYiCiAgICAgICAgIGlkPSJ0c3BhbjQ2MDkiCiAgICAgICAgIHNvZGlwb2RpOnJvbGU9ImxpbmUiPk1lcmlkaWFuPC90c3Bhbj48L3RleHQ+CiAgICA8dGV4dAogICAgICAgc29kaXBvZGk6bGluZXNwYWNpbmc9IjEyNSUiCiAgICAgICBpZD0idGV4dDQ5NzkiCiAgICAgICB5PSI2NDAuMjA1MjYiCiAgICAgICB4PSI1NzIuODMyMTUiCiAgICAgICBzdHlsZT0iZm9udC1zaXplOjkuNjU4Mzc3NjVweDtmb250LXN0eWxlOm5vcm1hbDtmb250LXZhcmlhbnQ6bm9ybWFsO2ZvbnQtd2VpZ2h0Om5vcm1hbDtmb250LXN0cmV0Y2g6bm9ybWFsO2xpbmUtaGVpZ2h0OjEyNSU7bGV0dGVyLXNwYWNpbmc6MHB4O3dvcmQtc3BhY2luZzowcHg7ZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lO2ZvbnQtZmFtaWx5OlZlcmRhbmE7LWlua3NjYXBlLWZvbnQtc3BlY2lmaWNhdGlvbjpWZXJkYW5hIgogICAgICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHRzcGFuCiAgICAgICAgIHk9IjY0MC4yMDUyNiIKICAgICAgICAgeD0iNTcyLjgzMjE1IgogICAgICAgICBpZD0idHNwYW40OTgxIgogICAgICAgICBzb2RpcG9kaTpyb2xlPSJsaW5lIj5DZW50cmFsPC90c3Bhbj48L3RleHQ+CiAgICA8dGV4dAogICAgICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIKICAgICAgIHN0eWxlPSJmb250LXNpemU6OS42NTgzNzc2NXB4O2ZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7bGluZS1oZWlnaHQ6MTI1JTtsZXR0ZXItc3BhY2luZzowcHg7d29yZC1zcGFjaW5nOjBweDtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7Zm9udC1mYW1pbHk6VmVyZGFuYTstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOlZlcmRhbmEiCiAgICAgICB4PSI1NzUuMDg5NjYiCiAgICAgICB5PSI2NzAuOTAzNSIKICAgICAgIGlkPSJ0ZXh0NDk4MyIKICAgICAgIHNvZGlwb2RpOmxpbmVzcGFjaW5nPSIxMjUlIj48dHNwYW4KICAgICAgICAgc29kaXBvZGk6cm9sZT0ibGluZSIKICAgICAgICAgaWQ9InRzcGFuNDk4NSIKICAgICAgICAgeD0iNTc1LjA4OTY2IgogICAgICAgICB5PSI2NzAuOTAzNSI+RG91Z2xhczwvdHNwYW4+PC90ZXh0PgogICAgPHRleHQKICAgICAgIHhtbDpzcGFjZT0icHJlc2VydmUiCiAgICAgICBzdHlsZT0iZm9udC1zaXplOjkuNjU4Mzc3NjVweDtmb250LXN0eWxlOm5vcm1hbDtmb250LXZhcmlhbnQ6bm9ybWFsO2ZvbnQtd2VpZ2h0Om5vcm1hbDtmb250LXN0cmV0Y2g6bm9ybWFsO2xpbmUtaGVpZ2h0OjEyNSU7bGV0dGVyLXNwYWNpbmc6MHB4O3dvcmQtc3BhY2luZzowcHg7ZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lO2ZvbnQtZmFtaWx5OlZlcmRhbmE7LWlua3NjYXBlLWZvbnQtc3BlY2lmaWNhdGlvbjpWZXJkYW5hIgogICAgICAgeD0iNDk5LjQ4OTYyIgogICAgICAgeT0iMTAwOC42MDY5IgogICAgICAgaWQ9InRleHQ1MDQ3IgogICAgICAgc29kaXBvZGk6bGluZXNwYWNpbmc9IjEyNSUiPjx0c3BhbgogICAgICAgICBzb2RpcG9kaTpyb2xlPSJsaW5lIgogICAgICAgICBpZD0idHNwYW41MDQ5IgogICAgICAgICB4PSI0OTkuNDg5NjIiCiAgICAgICAgIHk9IjEwMDguNjA2OSI+NDd0aDwvdHNwYW4+PC90ZXh0PgogICAgPHRleHQKICAgICAgIHhtbDpzcGFjZT0icHJlc2VydmUiCiAgICAgICBzdHlsZT0iZm9udC1zaXplOjkuNjU4Mzc3NjVweDtmb250LXN0eWxlOm5vcm1hbDtmb250LXZhcmlhbnQ6bm9ybWFsO2ZvbnQtd2VpZ2h0Om5vcm1hbDtmb250LXN0cmV0Y2g6bm9ybWFsO2xpbmUtaGVpZ2h0OjEyNSU7bGV0dGVyLXNwYWNpbmc6MHB4O3dvcmQtc3BhY2luZzowcHg7ZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lO2ZvbnQtZmFtaWx5OlZlcmRhbmE7LWlua3NjYXBlLWZvbnQtc3BlY2lmaWNhdGlvbjpWZXJkYW5hIgogICAgICAgeD0iMjE2LjY0NTQzIgogICAgICAgeT0iNzI1Ljk4Mjk3IgogICAgICAgaWQ9InRleHQ1MDUxIgogICAgICAgc29kaXBvZGk6bGluZXNwYWNpbmc9IjEyNSUiPjx0c3BhbgogICAgICAgICBzb2RpcG9kaTpyb2xlPSJsaW5lIgogICAgICAgICBpZD0idHNwYW41MDUzIgogICAgICAgICB4PSIyMTYuNjQ1NDMiCiAgICAgICAgIHk9IjcyNS45ODI5NyI+S2VsbG9nZzwvdHNwYW4+PC90ZXh0PgogICAgPGZsb3dSb290CiAgICAgICB4bWw6c3BhY2U9InByZXNlcnZlIgogICAgICAgaWQ9ImZsb3dSb290NTA1NSIKICAgICAgIHN0eWxlPSJmb250LXNpemU6MThweDtmb250LXN0eWxlOm5vcm1hbDtmb250LXZhcmlhbnQ6bm9ybWFsO2ZvbnQtd2VpZ2h0Om5vcm1hbDtmb250LXN0cmV0Y2g6bm9ybWFsO2xpbmUtaGVpZ2h0OjEyNSU7bGV0dGVyLXNwYWNpbmc6MHB4O3dvcmQtc3BhY2luZzowcHg7ZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lO2ZvbnQtZmFtaWx5OlZlcmRhbmE7LWlua3NjYXBlLWZvbnQtc3BlY2lmaWNhdGlvbjpWZXJkYW5hIgogICAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwyODcuMzYyMTgpIj48Zmxvd1JlZ2lvbgogICAgICAgICBpZD0iZmxvd1JlZ2lvbjUwNTciPjxyZWN0CiAgICAgICAgICAgaWQ9InJlY3Q1MDU5IgogICAgICAgICAgIHdpZHRoPSIzNDMuNTcxNDQiCiAgICAgICAgICAgaGVpZ2h0PSIxMDMuNTcxNDMiCiAgICAgICAgICAgeD0iMTkuMjg1NzE1IgogICAgICAgICAgIHk9IjE3LjE0Mjg1NyIKICAgICAgICAgICBzdHlsZT0iZm9udC1zdHlsZTpub3JtYWw7Zm9udC12YXJpYW50Om5vcm1hbDtmb250LXdlaWdodDpub3JtYWw7Zm9udC1zdHJldGNoOm5vcm1hbDtmb250LWZhbWlseTpWZXJkYW5hOy1pbmtzY2FwZS1mb250LXNwZWNpZmljYXRpb246VmVyZGFuYSIgLz48L2Zsb3dSZWdpb24+PGZsb3dQYXJhCiAgICAgICAgIGlkPSJmbG93UGFyYTUwNjEiPjwvZmxvd1BhcmE+PC9mbG93Um9vdD4gICAgPHRleHQKICAgICAgIHRyYW5zZm9ybT0ibWF0cml4KDAsMSwtMSwwLDAsMCkiCiAgICAgICBzb2RpcG9kaTpsaW5lc3BhY2luZz0iMTI1JSIKICAgICAgIGlkPSJ0ZXh0NDYwNy03IgogICAgICAgeT0iLTUwOC4xODk3MyIKICAgICAgIHg9Ijc3NC44NzU2MSIKICAgICAgIHN0eWxlPSJmb250LXNpemU6OS42NTgzNzc2NXB4O2ZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7bGluZS1oZWlnaHQ6MTI1JTtsZXR0ZXItc3BhY2luZzowcHg7d29yZC1zcGFjaW5nOjBweDtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7Zm9udC1mYW1pbHk6VmVyZGFuYTstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOlZlcmRhbmEiCiAgICAgICB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4KICAgICAgICAgeT0iLTUwOC4xODk3MyIKICAgICAgICAgeD0iNzc0Ljg3NTYxIgogICAgICAgICBpZD0idHNwYW40NjA5LTciCiAgICAgICAgIHNvZGlwb2RpOnJvbGU9ImxpbmUiPk1jQ2xlYW48L3RzcGFuPjwvdGV4dD4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iY29sb3I6IzAwMDAwMDtmaWxsOm5vbmU7c3Ryb2tlOiMzMzMzNjY7c3Ryb2tlLXdpZHRoOjFweDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1vcGFjaXR5OjE7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1kYXNob2Zmc2V0OjA7bWFya2VyOm5vbmU7dmlzaWJpbGl0eTp2aXNpYmxlO2Rpc3BsYXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7ZW5hYmxlLWJhY2tncm91bmQ6YWNjdW11bGF0ZSIKICAgICAgIGQ9Im0gMzY0LjE1OTk5LDY1OC40Mjg5MSAyOTkuNTEwMjMsLTEuMDEwMTYgYyA2LjQ5ODcyLC0wLjAyMTkgNi45NzcxOSw5LjI1NDEyIDE2LjU5NjMxLDkuMzkyNDcgMTIuMDU0MjcsMC4xNzMzOSAyOS4xMTA4MywtMC41MzU3MiA1NC4xMTQzNywtMC4zMDExIgogICAgICAgaWQ9InBhdGg1NDQwIgogICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsMjg3LjM2MjE4KSIKICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY3NzYyIgLz4KICAgIDx0ZXh0CiAgICAgICB4bWw6c3BhY2U9InByZXNlcnZlIgogICAgICAgc3R5bGU9ImZvbnQtc2l6ZTo5LjY1ODM3NzY1cHg7Zm9udC1zdHlsZTpub3JtYWw7Zm9udC12YXJpYW50Om5vcm1hbDtmb250LXdlaWdodDpub3JtYWw7Zm9udC1zdHJldGNoOm5vcm1hbDtsaW5lLWhlaWdodDoxMjUlO2xldHRlci1zcGFjaW5nOjBweDt3b3JkLXNwYWNpbmc6MHB4O2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZTtmb250LWZhbWlseTpWZXJkYW5hOy1pbmtzY2FwZS1mb250LXNwZWNpZmljYXRpb246VmVyZGFuYSIKICAgICAgIHg9IjM3My45OTMwNCIKICAgICAgIHk9Ijk0NC4zNTc1NCIKICAgICAgIGlkPSJ0ZXh0NTA0Ny05IgogICAgICAgc29kaXBvZGk6bGluZXNwYWNpbmc9IjEyNSUiPjx0c3BhbgogICAgICAgICBzb2RpcG9kaTpyb2xlPSJsaW5lIgogICAgICAgICBpZD0idHNwYW41MDQ5LTMiCiAgICAgICAgIHg9IjM3My45OTMwNCIKICAgICAgICAgeT0iOTQ0LjM1NzU0Ij5NYWNBcnRodXI8L3RzcGFuPjwvdGV4dD4KICAgIDx0ZXh0CiAgICAgICB0cmFuc2Zvcm09Im1hdHJpeCgwLDEsLTEsMCwwLDApIgogICAgICAgc29kaXBvZGk6bGluZXNwYWNpbmc9IjEyNSUiCiAgICAgICBpZD0idGV4dDQ2MDctNy0xIgogICAgICAgeT0iLTQ5MC4yNDU5NyIKICAgICAgIHg9Ijc4MC44NDYwNyIKICAgICAgIHN0eWxlPSJmb250LXNpemU6OS42NTgzNzc2NXB4O2ZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7bGluZS1oZWlnaHQ6MTI1JTtsZXR0ZXItc3BhY2luZzowcHg7d29yZC1zcGFjaW5nOjBweDtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7Zm9udC1mYW1pbHk6VmVyZGFuYTstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOlZlcmRhbmEiCiAgICAgICB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4KICAgICAgICAgeT0iLTQ5MC4yNDU5NyIKICAgICAgICAgeD0iNzgwLjg0NjA3IgogICAgICAgICBpZD0idHNwYW40NjA5LTctOSIKICAgICAgICAgc29kaXBvZGk6cm9sZT0ibGluZSI+U2VuZWNhPC90c3Bhbj48L3RleHQ+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImNvbG9yOiMwMDAwMDA7ZmlsbDpub25lO3N0cm9rZTojMzMzMzY2O3N0cm9rZS13aWR0aDoxcHg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW1pdGVybGltaXQ6NDtzdHJva2Utb3BhY2l0eToxO3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2UtZGFzaG9mZnNldDowO21hcmtlcjpub25lO3Zpc2liaWxpdHk6dmlzaWJsZTtkaXNwbGF5OmlubGluZTtvdmVyZmxvdzp2aXNpYmxlO2VuYWJsZS1iYWNrZ3JvdW5kOmFjY3VtdWxhdGUiCiAgICAgICBkPSJtIDM2Ny42OTU1Myw1MzcuMjEwNiAxNDEuMjgzMDMsLTEuMDEwMTUgYyA2LjQ4OTk5LC0wLjA0NjQgMTIuNzgxMTQsNy4yMzU0NSAxOS4xOTI5LDcuMzIzNiA1NS45MjM2MiwwLjc2ODkgMTU4LjY4OTk3LC0wLjE3MzMzIDIzNi41MTQwMiwtMS4wMTAxNSA3LjgzOTU2LC0wLjA4NDMgMjIuNjMxNDcsLTE5Ljg1MzU1IDMwLjMwNDU3LC0yMC40NTU1OSAyMi4yNjU4OSwtMS4zNTE4MSA0NS4xNzk0NSwtMC41MDUwNyA2Ny42ODAyMiwtMC41MDUwNyAxNi4xNDczMSwtMC42MzI0MSAzLjYxMDE2LDIwLjcwODEzIDI2Ljc2OTA0LDIwLjcwODEzIGwgMjQzLjQ0Njc5LC0xLjAxMDE2IgogICAgICAgaWQ9InBhdGg1NDk2IgogICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsMjg3LjM2MjE4KSIKICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY3NzY2NjY2MiIC8+CiAgICA8dGV4dAogICAgICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIKICAgICAgIHN0eWxlPSJmb250LXNpemU6OS42NTgzNzc2NXB4O2ZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7bGluZS1oZWlnaHQ6MTI1JTtsZXR0ZXItc3BhY2luZzowcHg7d29yZC1zcGFjaW5nOjBweDtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7Zm9udC1mYW1pbHk6VmVyZGFuYTstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOlZlcmRhbmEiCiAgICAgICB4PSI2ODUuMjA4MTMiCiAgICAgICB5PSI4MjcuNTMwODIiCiAgICAgICBpZD0idGV4dDQzMTAtNy04IgogICAgICAgc29kaXBvZGk6bGluZXNwYWNpbmc9IjEyNSUiPjx0c3BhbgogICAgICAgICBzb2RpcG9kaTpyb2xlPSJsaW5lIgogICAgICAgICBpZD0idHNwYW40MzEyLTYtNiIKICAgICAgICAgeD0iNjg1LjIwODEzIgogICAgICAgICB5PSI4MjcuNTMwODIiPlBhd25lZTwvdHNwYW4+PC90ZXh0PgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJjb2xvcjojMDAwMDAwO2ZpbGw6bm9uZTtzdHJva2U6IzMzMzM2NjtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLW9wYWNpdHk6MTtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLWRhc2hvZmZzZXQ6MDttYXJrZXI6bm9uZTt2aXNpYmlsaXR5OnZpc2libGU7ZGlzcGxheTppbmxpbmU7b3ZlcmZsb3c6dmlzaWJsZTtlbmFibGUtYmFja2dyb3VuZDphY2N1bXVsYXRlIgogICAgICAgZD0iTSA1NTQuMjg1NzIsNzIxLjQyODU3IDU1MCw1NDMuMjE0MjkgNTQ3LjE0Mjg2LDEwMi41IDU0Ni43ODU3MiwyMy4yMTQyODUiCiAgICAgICBpZD0icGF0aDU1MTkiCiAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwyODcuMzYyMTgpIiAvPgogICAgPHRleHQKICAgICAgIHhtbDpzcGFjZT0icHJlc2VydmUiCiAgICAgICBzdHlsZT0iZm9udC1zaXplOjkuNjU4Mzc3NjVweDtmb250LXN0eWxlOm5vcm1hbDtmb250LXZhcmlhbnQ6bm9ybWFsO2ZvbnQtd2VpZ2h0Om5vcm1hbDtmb250LXN0cmV0Y2g6bm9ybWFsO2xpbmUtaGVpZ2h0OjEyNSU7bGV0dGVyLXNwYWNpbmc6MHB4O3dvcmQtc3BhY2luZzowcHg7ZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lO2ZvbnQtZmFtaWx5OlZlcmRhbmE7LWlua3NjYXBlLWZvbnQtc3BlY2lmaWNhdGlvbjpWZXJkYW5hIgogICAgICAgeD0iNTI5LjYyNTMxIgogICAgICAgeT0iLTU1MC44NDc3OCIKICAgICAgIGlkPSJ0ZXh0NDU0My01IgogICAgICAgc29kaXBvZGk6bGluZXNwYWNpbmc9IjEyNSUiCiAgICAgICB0cmFuc2Zvcm09Im1hdHJpeCgwLDEsLTEsMCwwLDApIj48dHNwYW4KICAgICAgICAgc29kaXBvZGk6cm9sZT0ibGluZSIKICAgICAgICAgaWQ9InRzcGFuNDU0NS0wIgogICAgICAgICB4PSI1MjkuNjI1MzEiCiAgICAgICAgIHk9Ii01NTAuODQ3NzgiPkJyb2Fkd2F5PC90c3Bhbj48L3RleHQ+CiAgPC9nPgo8L3N2Zz4K\",\"xPosKeyName\":\"xPos\",\"yPosKeyName\":\"yPos\",\"posFunction\":\"return {x: origXPos, y: origYPos};\",\"markerOffsetX\":0.5,\"markerOffsetY\":1,\"showTooltip\":true,\"autocloseTooltip\":true,\"labelFunction\":\"var deviceType = dsData[dsIndex]['deviceType'];\\r\\nif (typeof deviceType !== undefined) {\\r\\n if (deviceType == \\\"energy meter\\\") {\\r\\n return '${entityName}, ${energy:2} kWt';\\r\\n } else if (deviceType == \\\"thermometer\\\") {\\r\\n return '${entityName}, ${temperature:2} °C';\\r\\n }\\r\\n}\",\"tooltipFunction\":\"var deviceType = dsData[dsIndex]['deviceType'];\\r\\nif (typeof deviceType !== undefined) {\\r\\n if (deviceType == \\\"energy meter\\\") {\\r\\n return '${entityName}
Energy: ${energy:2} kWt
';\\r\\n } else if (deviceType == \\\"thermometer\\\") {\\r\\n return '${entityName}
Temperature: ${temperature:2} °C
';\\r\\n }\\r\\n}\",\"playDelay\":500,\"keyNameDir\":\"wind_dir\",\"keyNamePower\":\"wind_speed\",\"arrowImageUrl\":\"data:image/png;base64,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\",\"ArrowLenAt4\":40,\"ArrowWidth\":40},\"title\":\"TimeseriesPolygonImageMap\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"legendConfig\":{\"position\":\"bottom\",\"showMin\":false,\"showMax\":false,\"showAvg\":true,\"showTotal\":false}}" 62 | } 63 | } 64 | ] 65 | } -------------------------------------------------------------------------------- /TimedependentPolygonmapTBWidget/maputils.json: -------------------------------------------------------------------------------- 1 | { 2 | "widgetsBundle": { 3 | "alias": "maputils", 4 | "title": "MapUtils", 5 | "image": null 6 | }, 7 | "widgetTypes": [ 8 | { 9 | "alias": "newwidget", 10 | "name": "NewWidget", 11 | "descriptor": { 12 | "type": "latest", 13 | "sizeX": 7.5, 14 | "sizeY": 6, 15 | "resources": [], 16 | "templateHtml": "", 17 | "templateCss": "", 18 | "controllerScript": "self.onInit = function() {\n self.ctx.map = new TbMapWidgetV2('openstreet-map',\n false, self.ctx);\n\n self.mapleaflet = self.ctx.map.map.map;\n //self.mapleaflet.panBy([200, 300]);\n self.mapleaflet.setView([32, 34.95], 9);\n\n\n\n}\n\n\nself.onDataUpdated = function() {\n\n for (var i in self.mapleaflet._layers) {\n if (self.mapleaflet._layers[i]._path !==\n undefined) {\n try {\n self.mapleaflet.removeLayer(self.mapleaflet\n ._layers[i]);\n } catch (e) {\n console.log(\"problem with \" + e + self.mapleaflet\n ._layers[i]);\n }\n }\n }\n\n\n for (i = 0; i < self.ctx.data.length; i++) {\n\n var dataitem = self.ctx.data[i];\n var value = String(dataitem.data[0][1]).replace(\n /'/g, '\"');\n\n var parsed = JSON.parse(value);\n\n try {\n geojsonFeature = parsed.features;\n } catch (err) {\n geojsonFeature = null\n }\n\n var myStyle = {\n \"color\": dataitem.dataKey.color,\n \"opacity\": 0.8\n };\n\n try {\n L.geoJSON(geojsonFeature, {\n style: myStyle\n }).addTo(self.mapleaflet);\n } catch (e) {\n console.log(e);\n }\n self.ctx.map.update();\n\n\n /*\n\n try {\n L.geoJSON(\n states, {\n style: function(\n feature\n ) {\n switch (\n feature\n .properties\n .party\n ) {\n case 'Republican':\n return {\n color: \"#ff0000\"\n };\n }\n }\n }).addTo(\n self.mapleaflet\n );\n console.log(\n \"Added Features\"\n );\n } catch (e) {\n console.log(e);\n\n }\n \n \n var states = [{\n \"type\": \"Feature\",\n \"properties\": {},\n \"geometry\": {\n \"type\": \"Polygon\",\n \"coordinates\": [\n [\n [32,\n 35.09981253905925\n ],\n [32, 35.1],\n [31.8, 34]\n ]\n ]\n }\n }];\n\n \n\n //L.polygon(parsed['x'], {\n // color: dataitem.dataKey.color,\n // fillColor: dataitem.dataKey.color,\n // fillOpacity: 0.5\n // }).addTo(self.mapleaflet);\n*/\n }\n\n}\n\n\nself.onResize = function() {\n self.ctx.map.resize();\n}\n\nself.getSettingsSchema = function() {\n var tbScheme = TbMapWidgetV2.settingsSchema(\n 'openstreet-map');\n\n return tbScheme;\n}\n\nself.getDataKeySettingsSchema =\n function() {\n return TbMapWidgetV2.dataKeySettingsSchema(\n 'openstreet-map');\n }\n\nself.actionSources = function() {\n return TbMapWidgetV2.actionSources();\n}\n\nself.onDestroy = function() {}", 19 | "settingsSchema": "{\n \"schema\": {\n \"typehttp://127.0.0.1:8080/#\": \"object\",\n \"title\": \"TimeseriesTableSettings\",\n \"properties\": {\n \"values\": {\n \"title\": \"Isopleth Names\",\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"required\": []\n },\n \"form\": [\n \"values\"\n ]\n}", 20 | "dataKeySettingsSchema": "", 21 | "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"NewWidget\"}" 22 | } 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /TimedependentPolygonmapTBWidget/polyimagemap.js: -------------------------------------------------------------------------------- 1 | self.onInit = function () { 2 | var elemap = $('#TimeseriesPolygonImageMap'); 3 | self.ctx.map = new TbMapWidgetV2('image-map', false, self.ctx, undefined, elemap); 4 | self.posFunc = self.ctx.map.map.posFunction; 5 | setTimeout(() => { 6 | self.mapleaflet = self.ctx.map.map.map; 7 | window.TimeseriesPolygonImageMapSelf = self; 8 | self.onResize(); 9 | self.onDataUpdated(); 10 | 11 | console.log(self); 12 | }, 10); 13 | }; 14 | 15 | self.resizeMap = function () { 16 | if (!self.mapleaflet || !self.mapleaflet._container) { 17 | return; 18 | } 19 | self.mapleaflet._container.style.height = (self.ctx.height - 30) + "px"; 20 | self.mapleaflet.invalidateSize(); 21 | }; 22 | 23 | self.clearPolygons = function () { 24 | if (!self.mapleaflet || !self.mapleaflet._layers) { 25 | return; 26 | } 27 | for (var i in self.mapleaflet._layers) { 28 | if (self.mapleaflet._layers[i]._path !== undefined) { 29 | try { 30 | self.mapleaflet.removeLayer(self.mapleaflet._layers[i]); 31 | } catch (e) { 32 | console.log("problem with " + e + self.mapleaflet._layers[i]); 33 | } 34 | } 35 | } 36 | }; 37 | 38 | function parseJson(json) { 39 | var value = String(json).replace(/'/g, '\"'); 40 | return JSON.parse(value); 41 | } 42 | 43 | function lerp(a, b, f) { 44 | return a + f * (b - a); 45 | } 46 | 47 | self.posOnMap = function (v) { 48 | const bounds = self.ctx.map.map.imageOverlay._bounds; 49 | var ne = bounds._northEast, 50 | sw = bounds._southWest; 51 | var pos = self.posFunc(v[0], v[1]); 52 | pos.y = lerp(ne.lat, sw.lat, pos.y); 53 | pos.x = lerp(sw.lng, ne.lng, pos.x); 54 | return [pos.x, pos.y]; 55 | }; 56 | 57 | self.posFuncGeoJson = function (geojson) { 58 | geojson.forEach((g) => { 59 | g.geometry.coordinates = g.geometry.coordinates.map(shape => shape.map(self.posOnMap)); 60 | }); 61 | return geojson; 62 | }; 63 | 64 | self.showPolygons = function (parsed, color) { 65 | try { 66 | var geojsonFeature = self.posFuncGeoJson(parsed.features); 67 | L.geoJSON(geojsonFeature, { 68 | style: { "color": color, "opacity": 0.8 } 69 | }).addTo(self.mapleaflet); 70 | } catch (e) { 71 | console.log(e); 72 | } 73 | }; 74 | 75 | self.getCurrIndex = function () { 76 | return parseInt($('#TimeFrame')[0].value); 77 | }; 78 | 79 | self.setCurrIndex = function (val) { 80 | $('#TimeFrame')[0].value = val; 81 | self.onDataUpdated(); 82 | }; 83 | 84 | self.getPolygonFrames = function () { 85 | const jsons = self.ctx.data.filter(prop => prop.data.length > 0 && typeof prop.data[0][1] === "string"); 86 | var maxRange = 0; 87 | const polygonFrames = jsons.map(prop => { 88 | const frames = parseJson(prop.data[0][1]); 89 | maxRange = Math.max(Math.max.apply(this, frames.map(b => b.index))); 90 | return { frames: frames, color: prop.dataKey.color }; 91 | }); 92 | return [polygonFrames, maxRange]; 93 | }; 94 | 95 | self.showPolygonFrame = function (polygonFrames, index) { 96 | let valueName = ''; 97 | self.clearPolygons(); 98 | polygonFrames.forEach(polys => { 99 | const poly = polys.frames.find(frame => parseInt(frame.index) === index); 100 | if (poly) { 101 | valueName = poly.name; 102 | self.showPolygons(poly.value, polys.color); 103 | } 104 | }); 105 | self.ctx.map.update(); 106 | return valueName ? valueName : ''; 107 | }; 108 | 109 | self.getDirectionTelemetry = function () { 110 | // console.log(self.ctx.data); 111 | const nonJsons = self.ctx.data.filter(prop => prop.data.length > 0 && typeof prop.data[0][1] !== "string"); 112 | let ret = {}; 113 | nonJsons.forEach(prop => { 114 | const t = prop.datasource.name; 115 | ret[t] = ret[t] || {}; 116 | const kind = prop.dataKey.name; 117 | ret[t][kind] = prop.data[0][1]; 118 | }); 119 | return Object.values(ret); 120 | }; 121 | 122 | self.showDirectionMarker = function (s) { 123 | if (s.latitude === undefined || s.longitude === undefined) return undefined; 124 | let dir = s[self.ctx.settings.keyNameDir]; 125 | if (dir === undefined) return undefined; 126 | let power = s[self.ctx.settings.keyNamePower]; 127 | if (power === undefined) power = 4; 128 | const powerSize = parseFloat(power) * self.ctx.settings.ArrowLenAt4 / 4; 129 | if (/(Win32|Win64)/i.test(navigator.platform)) dir = (dir + 180) % 360; 130 | 131 | const pos = self.posOnMap([s.latitude, s.longitude]); 132 | var marker = L.marker([pos[1], pos[0]], { 133 | icon: L.icon({ 134 | iconUrl: self.ctx.settings.arrowImageUrl, 135 | iconSize: [powerSize, parseFloat(self.ctx.settings.ArrowWidth)] 136 | }), 137 | rotationAngle: 360 - dir % 360, // counter clockwise 138 | rotationOrigin: 'center' 139 | }).addTo(self.mapleaflet); 140 | 141 | return marker; 142 | }; 143 | 144 | self.onDataUpdated = function () { 145 | try { 146 | const index = self.getCurrIndex(); 147 | [polygonFrames, maxRange] = self.getPolygonFrames(); 148 | $('#TimeFrame')[0].max = maxRange; 149 | $('#TimeFrameLabel')[0].value = self.showPolygonFrame(polygonFrames, index); 150 | } catch (err) { 151 | // console.log("JSON error:", self.ctx.data, err); 152 | // return; 153 | } 154 | 155 | (self.windMarkers || []).forEach(m => m.removeFrom(self.mapleaflet)); 156 | const dirs = self.getDirectionTelemetry(); 157 | self.windMarkers = dirs.map(self.showDirectionMarker); 158 | self.windMarkers = self.windMarkers.filter(a => a); 159 | }; 160 | 161 | self.setNext = function () { 162 | var maxRange = parseInt($('#TimeFrame')[0].max); 163 | if (maxRange >= 1) { 164 | self.setCurrIndex((self.getCurrIndex() + 1) % (maxRange + 1)); 165 | } else { 166 | self.setCurrIndex(0); 167 | } 168 | }; 169 | 170 | self.Play = function () { 171 | self.playing = !self.playing; 172 | $('#PlayButton').text(self.playing ? 'Stop' : 'Play'); 173 | if (self.playing) { 174 | var cont = () => { 175 | self.setNext(); 176 | if (self.playing) { 177 | setTimeout(cont, self.ctx.settings.playDelay); 178 | } 179 | }; 180 | cont(); 181 | } 182 | }; 183 | 184 | self.onResize = function () { 185 | self.ctx.map.resize(); 186 | self.resizeMap(); 187 | }; 188 | 189 | self.getSettingsSchema = function () { 190 | var tbScheme = JSON.parse(JSON.stringify(TbMapWidgetV2.settingsSchema('image-map'))); 191 | // console.log(tbScheme); 192 | tbScheme.form.unshift( 193 | "playDelay", 194 | "keyNameDir", 195 | "keyNamePower", 196 | "ArrowLenAt4", 197 | "ArrowWidth", 198 | { 199 | "key": "arrowImageUrl", 200 | "type": "image" 201 | } 202 | ); 203 | Object.assign(tbScheme.schema.properties, 204 | { 205 | "playDelay": { 206 | "title": "Play delay", 207 | "type": "number", 208 | "default": 500 209 | }, 210 | "keyNameDir": { 211 | "title": "Direction key name", 212 | "type": "string", 213 | "default": "wind_dir" 214 | }, 215 | "keyNamePower": { 216 | "title": "Power key name", 217 | "type": "string", 218 | "default": "wind_speed" 219 | }, 220 | "ArrowLenAt4": { 221 | "title": "Arrow length in pixels when power/speed is 4", 222 | "type": "number", 223 | "default": 40 224 | }, 225 | "ArrowWidth": { 226 | "title": "Arrow width in pixels", 227 | "type": "number", 228 | "default": 40 229 | }, 230 | "arrowImageUrl": { 231 | "title": "Arrow image", 232 | "type": "string", 233 | "default": "data:image/png;base64,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" 234 | } 235 | }); 236 | return tbScheme; 237 | }; 238 | self.getDataKeySettingsSchema = function () { 239 | return TbMapWidgetV2.dataKeySettingsSchema('image-map'); 240 | }; 241 | 242 | self.actionSources = function () { 243 | return TbMapWidgetV2.actionSources(); 244 | }; 245 | 246 | self.onDestroy = function () { }; 247 | 248 | (function () { 249 | // save these original methods before they are overwritten 250 | var proto_initIcon = L.Marker.prototype._initIcon; 251 | var proto_setPos = L.Marker.prototype._setPos; 252 | 253 | var oldIE = (L.DomUtil.TRANSFORM === 'msTransform'); 254 | 255 | L.Marker.addInitHook(function () { 256 | var iconOptions = this.options.icon && this.options.icon.options; 257 | var iconAnchor = iconOptions && this.options.icon.options.iconAnchor; 258 | if (iconAnchor) { 259 | iconAnchor = (iconAnchor[0] + 'px ' + iconAnchor[1] + 'px'); 260 | } 261 | this.options.rotationOrigin = this.options.rotationOrigin || iconAnchor || 'center bottom'; 262 | this.options.rotationAngle = this.options.rotationAngle || 0; 263 | 264 | // Ensure marker keeps rotated during dragging 265 | this.on('drag', function (e) { e.target._applyRotation(); }); 266 | }); 267 | 268 | L.Marker.include({ 269 | _initIcon: function () { 270 | proto_initIcon.call(this); 271 | }, 272 | 273 | _setPos: function (pos) { 274 | proto_setPos.call(this, pos); 275 | this._applyRotation(); 276 | }, 277 | 278 | _applyRotation: function () { 279 | if (this.options.rotationAngle) { 280 | this._icon.style[L.DomUtil.TRANSFORM + 'Origin'] = this.options.rotationOrigin; 281 | 282 | // console.log(this.options.rotationAngle); 283 | // if(oldIE) { 284 | // // for IE 9, use the 2D rotation 285 | // this._icon.style[L.DomUtil.TRANSFORM] = 'rotate(' + this.options.rotationAngle + 'deg)'; 286 | // } else { 287 | // // for modern browsers, prefer the 3D accelerated version 288 | this._icon.style[L.DomUtil.TRANSFORM] = 289 | this._icon.style[L.DomUtil.TRANSFORM].replace(/ rotateZ\(\d+deg\)/g, '') 290 | + ' rotateZ(' + this.options.rotationAngle + 'deg)'; 291 | // } 292 | } 293 | }, 294 | 295 | setRotationAngle: function (angle) { 296 | this.options.rotationAngle = angle; 297 | this.update(); 298 | return this; 299 | }, 300 | 301 | setRotationOrigin: function (origin) { 302 | this.options.rotationOrigin = origin; 303 | this.update(); 304 | return this; 305 | } 306 | }); 307 | })(); 308 | -------------------------------------------------------------------------------- /TimedependentPolygonmapTBWidget/pubsubGeoJSON.py: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | import paho.mqtt.client as mqtt 17 | from time import sleep 18 | import random 19 | import numpy 20 | import geopandas 21 | from shapely.geometry import Point, Polygon 22 | import pprint 23 | import json 24 | 25 | broker="192.116.82.80" 26 | topic_pub='v1/devices/me/telemetry' 27 | 28 | attr_pub ='v1/devices/me/attributes' 29 | 30 | def on_message(client, userdata, message): 31 | print("message received " ,str(message.payload.decode("utf-8"))) 32 | print("message topic=",message.topic) 33 | print("message qos=",message.qos) 34 | print("message retain flag=",message.retain) 35 | 36 | def on_connect(client, userdata, flags, rc): 37 | print("Connected flags"+str(flags)+"result code " +str(rc)) 38 | 39 | 40 | class Device(object): 41 | 42 | def __init__(self,name): 43 | self.name = name 44 | self.client = mqtt.Client() 45 | self.client.username_pw_set(name) 46 | self.client.on_message=on_message 47 | self.client.on_connect= on_connect 48 | self.client.connect('192.116.82.80', 1883, 1) 49 | self.client.loop_start() 50 | 51 | def pub(self): 52 | 53 | R1 = random.randrange(3, 20)/100. 54 | R2 = R1 + random.randrange(3, 20)/100. 55 | R3 = R2 + random.randrange(3, 20)/100. 56 | 57 | angle = numpy.arange(0,2*numpy.pi,0.05) 58 | baseX = 35 59 | baseY = 32.5 60 | 61 | X1 = baseX + R1*numpy.sin(angle) 62 | Y1 = baseY + R1*numpy.cos(angle) 63 | A1 = Polygon([[i[0],i[1]] for i in zip(X1,Y1)]) 64 | A1s = str([[i[0],i[1]] for i in zip(X1,Y1)]) 65 | 66 | 67 | X2 = baseX + R2*numpy.sin(angle) 68 | Y2 = baseY + R2*numpy.cos(angle) 69 | A2 = Polygon([[i[0],i[1]] for i in zip(X2,Y2)]) 70 | 71 | 72 | X3 = baseX + R3*numpy.sin(angle) 73 | Y3 = baseY + R3*numpy.cos(angle) 74 | A3 = Polygon([[i[0],i[1]] for i in zip(X3,Y3)]) 75 | 76 | G = geopandas.GeoDataFrame({'geometry' : [A1,A2,A3]}) 77 | 78 | totalD = G.diff() 79 | totalD.iloc[0] = G.iloc[0] 80 | 81 | jsonL = [] 82 | for name,i in zip(["A","B","C"],range(totalD.size)): 83 | jsonL.append('"%s" : "%s"' %(name, totalD.iloc[i:i+1].to_json().replace('"',"'"))) 84 | 85 | msg = '{%s}' % ",".join(jsonL) 86 | self.client.publish(topic_pub, msg) 87 | 88 | deviceName = ["A1_TEST_TOKEN"] 89 | DeviceList = [Device(x) for x in deviceName] 90 | 91 | while True: 92 | for d in DeviceList: 93 | d.pub() 94 | sleep(1) 95 | -------------------------------------------------------------------------------- /TimedependentPolygonmapTBWidget/pubsubGeoJSONmulti.1.py: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | import paho.mqtt.client as mqtt 17 | from time import sleep 18 | import random 19 | import numpy 20 | import geopandas 21 | from shapely.geometry import Point, Polygon 22 | import pprint 23 | import json 24 | 25 | broker = "192.116.82.80" 26 | topic_pub = 'v1/devices/me/telemetry' 27 | 28 | attr_pub = 'v1/devices/me/attributes' 29 | 30 | 31 | def on_message(client, userdata, message): 32 | print("message received ", str(message.payload.decode("utf-8"))) 33 | print("message topic=", message.topic) 34 | print("message qos=", message.qos) 35 | print("message retain flag=", message.retain) 36 | 37 | 38 | def on_connect(client, userdata, flags, rc): 39 | print("Connected flags"+str(flags)+"result code " + str(rc)) 40 | 41 | 42 | class Device(object): 43 | 44 | def __init__(self, name): 45 | self.name = name 46 | self.client = mqtt.Client() 47 | self.client.username_pw_set(name) 48 | self.client.on_message = on_message 49 | self.client.on_connect = on_connect 50 | self.client.connect('192.116.82.80', 1883, 1) 51 | self.client.loop_start() 52 | 53 | def oneFrame(self): 54 | 55 | R1 = random.randrange(3, 20)/100. 56 | R2 = R1 + random.randrange(3, 20)/100. 57 | R3 = R2 + random.randrange(3, 20)/100. 58 | 59 | angle = numpy.arange(0, 2*numpy.pi, 0.05) 60 | baseX = 35 61 | baseY = 32.5 62 | 63 | X1 = baseX + R1*numpy.sin(angle) 64 | Y1 = baseY + R1*numpy.cos(angle) 65 | A1 = Polygon([[i[0], i[1]] for i in zip(X1, Y1)]) 66 | A1s = str([[i[0], i[1]] for i in zip(X1, Y1)]) 67 | 68 | X2 = baseX + R2*numpy.sin(angle) 69 | Y2 = baseY + R2*numpy.cos(angle) 70 | A2 = Polygon([[i[0], i[1]] for i in zip(X2, Y2)]) 71 | 72 | X3 = baseX + R3*numpy.sin(angle) 73 | Y3 = baseY + R3*numpy.cos(angle) 74 | A3 = Polygon([[i[0], i[1]] for i in zip(X3, Y3)]) 75 | 76 | G = geopandas.GeoDataFrame({'geometry': [A1, A2, A3]}) 77 | 78 | totalD = G.diff() 79 | totalD.iloc[0] = G.iloc[0] 80 | 81 | jsonL = [] 82 | for name, i in zip(["A", "B", "C"], range(totalD.size)): 83 | jsonL.append('"%s" : "%s"' % 84 | (name, totalD.iloc[i:i+1].to_json().replace('"', "'"))) 85 | 86 | return '{%s}' % ",".join(jsonL) 87 | 88 | def timeFrames(self): 89 | jsonL = [] 90 | for idx in range(3): 91 | jsonL.append('{"name": "%s", "value": %s}' % (idx, self.oneFrame())) 92 | 93 | return '[%s]' % ",".join(jsonL) 94 | 95 | def pub(self): 96 | msg = self.timeFrames() 97 | # print(msg) 98 | # exit() 99 | self.client.publish(topic_pub, msg) 100 | 101 | 102 | deviceName = ["A1_TEST_TOKEN"] 103 | DeviceList = [Device(x) for x in deviceName] 104 | 105 | while True: 106 | for d in DeviceList: 107 | d.pub() 108 | sleep(1) 109 | -------------------------------------------------------------------------------- /TimedependentPolygonmapTBWidget/pubsubGeoJSONmulti.py: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | import paho.mqtt.client as mqtt 17 | from time import sleep 18 | import random 19 | import numpy 20 | import geopandas 21 | from shapely.geometry import Point, Polygon 22 | import pprint 23 | import json 24 | 25 | broker = "192.116.82.80" 26 | topic_pub = 'v1/devices/me/telemetry' 27 | 28 | attr_pub = 'v1/devices/me/attributes' 29 | 30 | 31 | def on_message(client, userdata, message): 32 | print("message received ", str(message.payload.decode("utf-8"))) 33 | print("message topic=", message.topic) 34 | print("message qos=", message.qos) 35 | print("message retain flag=", message.retain) 36 | 37 | 38 | def on_connect(client, userdata, flags, rc): 39 | print("Connected flags"+str(flags)+"result code " + str(rc)) 40 | 41 | 42 | class Device(object): 43 | 44 | def __init__(self, name): 45 | self.name = name 46 | self.client = mqtt.Client() 47 | self.client.username_pw_set(name) 48 | self.client.on_message = on_message 49 | self.client.on_connect = on_connect 50 | self.client.connect('192.116.82.80', 1883, 1) 51 | self.client.loop_start() 52 | 53 | def pub(self): 54 | 55 | props = {"A": [], "B": [], "C": []} 56 | for idx in range(10): 57 | R1 = random.randrange(3, 20)*4 58 | R2 = R1 + random.randrange(3, 20)*4 59 | R3 = R2 + random.randrange(3, 20)*4 60 | 61 | angle = numpy.arange(0, 2*numpy.pi, 0.8) 62 | image_bounds = {'mn': {'x': 154994, 'y': 563428}, 'mx': {'x': 155784, 'y': 564085}} 63 | baseX = (image_bounds['mn']['x'] + image_bounds['mx']['x'])/2 64 | baseY = (image_bounds['mn']['y'] + image_bounds['mx']['y'])/2 65 | # baseX = 154994 # min x 66 | # baseY = 563428 # min y 67 | # baseX = 155784 # max x 68 | # baseY = 564085 # max y 69 | 70 | X1 = baseX + R1*numpy.sin(angle) 71 | Y1 = baseY + R1*numpy.cos(angle) 72 | A1 = Polygon([[i[0], i[1]] for i in zip(X1, Y1)]) 73 | A1s = str([[i[0], i[1]] for i in zip(X1, Y1)]) 74 | 75 | X2 = baseX + R2*numpy.sin(angle) 76 | Y2 = baseY + R2*numpy.cos(angle) 77 | A2 = Polygon([[i[0], i[1]] for i in zip(X2, Y2)]) 78 | 79 | X3 = baseX + R3*numpy.sin(angle) 80 | Y3 = baseY + R3*numpy.cos(angle) 81 | A3 = Polygon([[i[0], i[1]] for i in zip(X3, Y3)]) 82 | 83 | G = geopandas.GeoDataFrame({'geometry': [A1, A2, A3]}) 84 | 85 | totalD = G.diff() 86 | totalD.iloc[0] = G.iloc[0] 87 | 88 | for name, i in zip(["A", "B", "C"], range(totalD.size)): 89 | poly = totalD.iloc[i:i+1].to_json().replace('"', "'") 90 | sindex = idx 91 | polyindex = "{'index':'%s', 'name':'%s', 'value':%s}" % (idx, sindex, poly) 92 | props[name].append(polyindex) 93 | 94 | textprops = [] 95 | for name in props: 96 | # proptext = props[name].to_json().replace('"', "'") 97 | # textprops.append('"%s": %s' % (name, proptext)) 98 | 99 | text = '[%s]' % ",".join(props[name]) 100 | textprops.append('"%s": "%s"' % (name, text)) 101 | 102 | msg = '{%s}' % ",".join(textprops) 103 | # print(msg) 104 | # exit() 105 | self.client.publish(topic_pub, msg) 106 | 107 | 108 | deviceName = ["A1_TEST_TOKEN"] 109 | DeviceList = [Device(x) for x in deviceName] 110 | 111 | while True: 112 | for d in DeviceList: 113 | d.pub() 114 | sleep(5) 115 | -------------------------------------------------------------------------------- /TimedependentPolygonmapTBWidget/pubsubWind.py: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | import paho.mqtt.client as mqtt 17 | from time import sleep 18 | import random 19 | import numpy 20 | import pprint 21 | import json 22 | 23 | broker = "192.116.82.80" 24 | topic_pub = 'v1/devices/me/telemetry' 25 | 26 | attr_pub = 'v1/devices/me/attributes' 27 | 28 | 29 | def on_message(client, userdata, message): 30 | print("message received ", str(message.payload.decode("utf-8"))) 31 | print("message topic=", message.topic) 32 | print("message qos=", message.qos) 33 | print("message retain flag=", message.retain) 34 | 35 | 36 | def on_connect(client, userdata, flags, rc): 37 | print("Connected flags"+str(flags)+"result code " + str(rc)) 38 | 39 | class Device(object): 40 | 41 | def __init__(self, name): 42 | self.i = 10 43 | self.name = name 44 | self.client = mqtt.Client() 45 | self.client.username_pw_set(name) 46 | self.client.on_message = on_message 47 | self.client.on_connect = on_connect 48 | self.client.connect('192.116.82.80', 1883, 1) 49 | self.client.loop_start() 50 | 51 | def pub(self): 52 | 53 | props = {"wind_dir": self.i, "wind_speed": 10} # random.randrange(0,10)} 54 | self.i = (self.i + 10) % 360 55 | # props = {"wind_dir": 90.0, "wind_speed": 10} # random.randrange(0,10)} 56 | # props = {"wind_dir": random.randrange(0,360), "wind_speed": 10} # random.randrange(0,10)} 57 | msg = '%s' % str(props) 58 | self.client.publish(topic_pub, msg) 59 | 60 | 61 | deviceName = ["Wind1","Wind2","Wind3"] 62 | DeviceList = [Device(x) for x in deviceName] 63 | 64 | while True: 65 | for d in DeviceList: 66 | d.pub() 67 | sleep(1) 68 | 69 | -------------------------------------------------------------------------------- /js/PolygonMap.js: -------------------------------------------------------------------------------- 1 | export default widget = {}; 2 | 3 | widget.onInit = function() { 4 | widget.ctx.map = new TbMapWidgetV2('openstreet-map', false, widget.ctx); 5 | widget.mapleaflet = widget.ctx.map.map.map; 6 | //widget.mapleaflet.panBy([200, 300]); 7 | widget.mapleaflet.setView([32, 34.95], 9); 8 | } 9 | 10 | widget.onDataUpdated = function() { 11 | for (var i in widget.mapleaflet._layers) { 12 | if (widget.mapleaflet._layers[i]._path !== 13 | undefined) { 14 | try { 15 | widget.mapleaflet.removeLayer(widget.mapleaflet 16 | ._layers[i]); 17 | } catch (e) { 18 | console.log("problem with " + e + widget.mapleaflet._layers[i]); 19 | } 20 | 21 | } 22 | } 23 | 24 | for (i = 0; i < widget.ctx.data.length; i++) { 25 | try { 26 | var dataitem = widget.ctx.data[i]; 27 | var value = String(dataitem.data[0][1]).replace(/'/g, '\"'); 28 | var parsed = JSON.parse(value); 29 | geojsonFeature = parsed.features; 30 | } catch (err) { 31 | geojsonFeature = null 32 | } 33 | var myStyle = { 34 | "color": dataitem.dataKey.color, 35 | "opacity": 0.8 36 | }; 37 | try { 38 | L.geoJSON(geojsonFeature, { 39 | style: myStyle 40 | }).addTo(widget.mapleaflet); 41 | } catch (e) { 42 | console.log(e); 43 | } 44 | widget.ctx.map.update(); 45 | } 46 | } 47 | widget.onResize = function() { 48 | widget.ctx.map.resize(); 49 | } 50 | widget.getSettingsSchema = function() { 51 | var tbScheme = TbMapWidgetV2.settingsSchema( 52 | 'openstreet-map'); 53 | return tbScheme; 54 | } 55 | widget.getDataKeySettingsSchema = function() { 56 | return TbMapWidgetV2.dataKeySettingsSchema('openstreet-map'); 57 | } 58 | widget.actionSources = function() { 59 | return TbMapWidgetV2.actionSources(); 60 | } 61 | widget.onDestroy = function() {} 62 | -------------------------------------------------------------------------------- /js/PolygonMap_orig.js: -------------------------------------------------------------------------------- 1 | self.onInit = function() { 2 | console.log(self); 3 | self.ctx.map = new TbMapWidgetV2('openstreet-map', false, self.ctx); 4 | self.mapleaflet = self.ctx.map.map.map; 5 | //self.mapleaflet.panBy([200, 300]); 6 | self.mapleaflet.setView([32, 34.95], 9); 7 | } 8 | 9 | self.onDataUpdated = function() { 10 | for (var i in self.mapleaflet._layers) { 11 | if (self.mapleaflet._layers[i]._path !== 12 | undefined) { 13 | try { 14 | self.mapleaflet.removeLayer(self.mapleaflet 15 | ._layers[i]); 16 | } catch (e) { 17 | console.log("problem with " + e + self.mapleaflet._layers[i]); 18 | } 19 | 20 | } 21 | } 22 | 23 | for (i = 0; i < self.ctx.data.length; i++) { 24 | try { 25 | var dataitem = self.ctx.data[i]; 26 | var value = String(dataitem.data[0][1]).replace(/'/g, '\"'); 27 | var parsed = JSON.parse(value); 28 | geojsonFeature = parsed.features; 29 | } catch (err) { 30 | geojsonFeature = null 31 | } 32 | var myStyle = { 33 | "color": dataitem.dataKey.color, 34 | "opacity": 0.8 35 | }; 36 | try { 37 | L.geoJSON(geojsonFeature, { 38 | style: myStyle 39 | }).addTo(self.mapleaflet); 40 | } catch (e) { 41 | console.log(e); 42 | } 43 | self.ctx.map.update(); 44 | } 45 | } 46 | self.onResize = function() { 47 | self.ctx.map.resize(); 48 | } 49 | self.getSettingsSchema = function() { 50 | var tbScheme = TbMapWidgetV2.settingsSchema( 51 | 'openstreet-map'); 52 | return tbScheme; 53 | } 54 | self.getDataKeySettingsSchema = function() { 55 | return TbMapWidgetV2.dataKeySettingsSchema('openstreet-map'); 56 | } 57 | self.actionSources = function() { 58 | return TbMapWidgetV2.actionSources(); 59 | } 60 | self.onDestroy = function() {} 61 | -------------------------------------------------------------------------------- /widgets/PolygonMap.js: -------------------------------------------------------------------------------- 1 | self.onInit = function() { 2 | self.ctx.map = new TbMapWidgetV2('openstreet-map', false, self.ctx); 3 | self.mapleaflet = self.ctx.map.map.map; 4 | //self.mapleaflet.panBy([200, 300]); 5 | self.mapleaflet.setView([32, 34.95], 9); 6 | } 7 | 8 | self.onDataUpdated = function() { 9 | for (var i in self.mapleaflet._layers) { 10 | if (self.mapleaflet._layers[i]._path !== 11 | undefined) { 12 | try { 13 | self.mapleaflet.removeLayer(self.mapleaflet 14 | ._layers[i]); 15 | } catch (e) { 16 | console.log("problem with " + e + self.mapleaflet._layers[i]); 17 | } 18 | 19 | } 20 | } 21 | 22 | for (i = 0; i < self.ctx.data.length; i++) { 23 | try { 24 | var dataitem = self.ctx.data[i]; 25 | var value = String(dataitem.data[0][1]).replace(/'/g, '\"'); 26 | var parsed = JSON.parse(value); 27 | geojsonFeature = parsed.features; 28 | } catch (err) { 29 | geojsonFeature = null 30 | } 31 | var myStyle = { 32 | "color": dataitem.dataKey.color, 33 | "opacity": 0.8 34 | }; 35 | try { 36 | L.geoJSON(geojsonFeature, { 37 | style: myStyle 38 | }).addTo(self.mapleaflet); 39 | } catch (e) { 40 | console.log(e); 41 | } 42 | self.ctx.map.update(); 43 | } 44 | } 45 | self.onResize = function() { 46 | self.ctx.map.resize(); 47 | } 48 | self.getSettingsSchema = function() { 49 | var tbScheme = TbMapWidgetV2.settingsSchema( 50 | 'openstreet-map'); 51 | return tbScheme; 52 | } 53 | self.getDataKeySettingsSchema = function() { 54 | return TbMapWidgetV2.dataKeySettingsSchema('openstreet-map'); 55 | } 56 | self.actionSources = function() { 57 | return TbMapWidgetV2.actionSources(); 58 | } 59 | self.onDestroy = function() {} 60 | -------------------------------------------------------------------------------- /widgets/TimeseriesPolygonImageMap.js: -------------------------------------------------------------------------------- 1 | self.onInit = function () { 2 | var elemap = $('#TimeseriesPolygonImageMap'); 3 | self.ctx.map = new TbMapWidgetV2('image-map', false, self.ctx, undefined, elemap); 4 | self.posFunc = self.ctx.map.map.posFunction; 5 | setTimeout(() => { 6 | self.mapleaflet = self.ctx.map.map.map; 7 | window.TimeseriesPolygonImageMapSelf = self; 8 | self.onResize(); 9 | self.onDataUpdated(); 10 | 11 | console.log(self); 12 | }, 10); 13 | }; 14 | 15 | self.resizeMap = function () { 16 | if (!self.mapleaflet || !self.mapleaflet._container) { 17 | return; 18 | } 19 | self.mapleaflet._container.style.height = (self.ctx.height - 30) + "px"; 20 | self.mapleaflet.invalidateSize(); 21 | }; 22 | 23 | self.clearPolygons = function () { 24 | if (!self.mapleaflet || !self.mapleaflet._layers) { 25 | return; 26 | } 27 | for (var i in self.mapleaflet._layers) { 28 | if (self.mapleaflet._layers[i]._path !== undefined) { 29 | try { 30 | self.mapleaflet.removeLayer(self.mapleaflet._layers[i]); 31 | } catch (e) { 32 | console.log("problem with " + e + self.mapleaflet._layers[i]); 33 | } 34 | } 35 | } 36 | }; 37 | 38 | function parseJson(json) { 39 | var value = String(json).replace(/'/g, '\"'); 40 | return JSON.parse(value); 41 | } 42 | 43 | function lerp(a, b, f) { 44 | return a + f * (b - a); 45 | } 46 | 47 | self.posOnMap = function (v) { 48 | const bounds = self.ctx.map.map.imageOverlay._bounds; 49 | var ne = bounds._northEast, 50 | sw = bounds._southWest; 51 | var pos = self.posFunc(v[0], v[1]); 52 | pos.y = lerp(ne.lat, sw.lat, pos.y); 53 | pos.x = lerp(sw.lng, ne.lng, pos.x); 54 | return [pos.x, pos.y]; 55 | }; 56 | 57 | self.posFuncGeoJson = function (geojson) { 58 | geojson.forEach((g) => { 59 | g.geometry.coordinates = g.geometry.coordinates.map(shape => shape.map(self.posOnMap)); 60 | }); 61 | return geojson; 62 | }; 63 | 64 | self.showPolygons = function (parsed, color) { 65 | try { 66 | var geojsonFeature = self.posFuncGeoJson(parsed.features); 67 | L.geoJSON(geojsonFeature, { 68 | style: { "color": color, "opacity": 0.8 } 69 | }).addTo(self.mapleaflet); 70 | } catch (e) { 71 | console.log(e); 72 | } 73 | }; 74 | 75 | self.getCurrIndex = function () { 76 | return parseInt($('#TimeFrame')[0].value); 77 | }; 78 | 79 | self.setCurrIndex = function (val) { 80 | $('#TimeFrame')[0].value = val; 81 | self.onDataUpdated(); 82 | }; 83 | 84 | self.getPolygonFrames = function () { 85 | const jsons = self.ctx.data.filter(prop => prop.data.length > 0 && typeof prop.data[0][1] === "string"); 86 | var maxRange = 0; 87 | const polygonFrames = jsons.map(prop => { 88 | const frames = parseJson(prop.data[0][1]); 89 | maxRange = Math.max(Math.max.apply(this, frames.map(b => b.index))); 90 | return { frames: frames, color: prop.dataKey.color }; 91 | }); 92 | return [polygonFrames, maxRange]; 93 | }; 94 | 95 | self.showPolygonFrame = function (polygonFrames, index) { 96 | let valueName = ''; 97 | self.clearPolygons(); 98 | polygonFrames.forEach(polys => { 99 | const poly = polys.frames.find(frame => parseInt(frame.index) === index); 100 | if (poly) { 101 | valueName = poly.name; 102 | self.showPolygons(poly.value, polys.color); 103 | } 104 | }); 105 | self.ctx.map.update(); 106 | return valueName ? valueName : ''; 107 | }; 108 | 109 | self.getDirectionTelemetry = function () { 110 | // console.log(self.ctx.data); 111 | const nonJsons = self.ctx.data.filter(prop => prop.data.length > 0 && typeof prop.data[0][1] !== "string"); 112 | let ret = {}; 113 | nonJsons.forEach(prop => { 114 | const t = prop.datasource.name; 115 | ret[t] = ret[t] || {}; 116 | const kind = prop.dataKey.name; 117 | ret[t][kind] = prop.data[0][1]; 118 | }); 119 | return Object.values(ret); 120 | }; 121 | 122 | self.showDirectionMarker = function (s) { 123 | if (s.latitude === undefined || s.longitude === undefined) return undefined; 124 | let dir = s[self.ctx.settings.keyNameDir]; 125 | if (dir === undefined) return undefined; 126 | let power = s[self.ctx.settings.keyNamePower]; 127 | if (power === undefined) power = 4; 128 | const powerSize = parseFloat(power) * self.ctx.settings.ArrowLenAt4 / 4; 129 | if (/(Win32|Win64)/i.test(navigator.platform)) dir = (dir + 180) % 360; 130 | 131 | const pos = self.posOnMap([s.latitude, s.longitude]); 132 | var marker = L.marker([pos[1], pos[0]], { 133 | icon: L.icon({ 134 | iconUrl: self.ctx.settings.arrowImageUrl, 135 | iconSize: [powerSize, parseFloat(self.ctx.settings.ArrowWidth)] 136 | }), 137 | rotationAngle: 360 - dir % 360, // counter clockwise 138 | rotationOrigin: 'center' 139 | }).addTo(self.mapleaflet); 140 | 141 | return marker; 142 | }; 143 | 144 | self.onDataUpdated = function () { 145 | try { 146 | const index = self.getCurrIndex(); 147 | [polygonFrames, maxRange] = self.getPolygonFrames(); 148 | $('#TimeFrame')[0].max = maxRange; 149 | $('#TimeFrameLabel')[0].value = self.showPolygonFrame(polygonFrames, index); 150 | } catch (err) { 151 | // console.log("JSON error:", self.ctx.data, err); 152 | // return; 153 | } 154 | 155 | (self.windMarkers || []).forEach(m => m.removeFrom(self.mapleaflet)); 156 | const dirs = self.getDirectionTelemetry(); 157 | self.windMarkers = dirs.map(self.showDirectionMarker); 158 | self.windMarkers = self.windMarkers.filter(a => a); 159 | }; 160 | 161 | self.setNext = function () { 162 | var maxRange = parseInt($('#TimeFrame')[0].max); 163 | if (maxRange >= 1) { 164 | self.setCurrIndex((self.getCurrIndex() + 1) % (maxRange + 1)); 165 | } else { 166 | self.setCurrIndex(0); 167 | } 168 | }; 169 | 170 | self.Play = function () { 171 | self.playing = !self.playing; 172 | $('#PlayButton').text(self.playing ? 'Stop' : 'Play'); 173 | if (self.playing) { 174 | var cont = () => { 175 | self.setNext(); 176 | if (self.playing) { 177 | setTimeout(cont, self.ctx.settings.playDelay); 178 | } 179 | }; 180 | cont(); 181 | } 182 | }; 183 | 184 | self.onResize = function () { 185 | self.ctx.map.resize(); 186 | self.resizeMap(); 187 | }; 188 | 189 | self.getSettingsSchema = function () { 190 | var tbScheme = JSON.parse(JSON.stringify(TbMapWidgetV2.settingsSchema('image-map'))); 191 | // console.log(tbScheme); 192 | tbScheme.form.unshift( 193 | "playDelay", 194 | "keyNameDir", 195 | "keyNamePower", 196 | "ArrowLenAt4", 197 | "ArrowWidth", 198 | { 199 | "key": "arrowImageUrl", 200 | "type": "image" 201 | } 202 | ); 203 | Object.assign(tbScheme.schema.properties, 204 | { 205 | "playDelay": { 206 | "title": "Play delay", 207 | "type": "number", 208 | "default": 500 209 | }, 210 | "keyNameDir": { 211 | "title": "Direction key name", 212 | "type": "string", 213 | "default": "wind_dir" 214 | }, 215 | "keyNamePower": { 216 | "title": "Power key name", 217 | "type": "string", 218 | "default": "wind_speed" 219 | }, 220 | "ArrowLenAt4": { 221 | "title": "Arrow length in pixels when power/speed is 4", 222 | "type": "number", 223 | "default": 40 224 | }, 225 | "ArrowWidth": { 226 | "title": "Arrow width in pixels", 227 | "type": "number", 228 | "default": 40 229 | }, 230 | "arrowImageUrl": { 231 | "title": "Arrow image", 232 | "type": "string", 233 | "default": "data:image/png;base64,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" 234 | } 235 | }); 236 | return tbScheme; 237 | }; 238 | self.getDataKeySettingsSchema = function () { 239 | return TbMapWidgetV2.dataKeySettingsSchema('image-map'); 240 | }; 241 | 242 | self.actionSources = function () { 243 | return TbMapWidgetV2.actionSources(); 244 | }; 245 | 246 | self.onDestroy = function () { }; 247 | 248 | (function () { 249 | // save these original methods before they are overwritten 250 | var proto_initIcon = L.Marker.prototype._initIcon; 251 | var proto_setPos = L.Marker.prototype._setPos; 252 | 253 | var oldIE = (L.DomUtil.TRANSFORM === 'msTransform'); 254 | 255 | L.Marker.addInitHook(function () { 256 | var iconOptions = this.options.icon && this.options.icon.options; 257 | var iconAnchor = iconOptions && this.options.icon.options.iconAnchor; 258 | if (iconAnchor) { 259 | iconAnchor = (iconAnchor[0] + 'px ' + iconAnchor[1] + 'px'); 260 | } 261 | this.options.rotationOrigin = this.options.rotationOrigin || iconAnchor || 'center bottom'; 262 | this.options.rotationAngle = this.options.rotationAngle || 0; 263 | 264 | // Ensure marker keeps rotated during dragging 265 | this.on('drag', function (e) { e.target._applyRotation(); }); 266 | }); 267 | 268 | L.Marker.include({ 269 | _initIcon: function () { 270 | proto_initIcon.call(this); 271 | }, 272 | 273 | _setPos: function (pos) { 274 | proto_setPos.call(this, pos); 275 | this._applyRotation(); 276 | }, 277 | 278 | _applyRotation: function () { 279 | if (this.options.rotationAngle) { 280 | this._icon.style[L.DomUtil.TRANSFORM + 'Origin'] = this.options.rotationOrigin; 281 | 282 | // console.log(this.options.rotationAngle); 283 | // if(oldIE) { 284 | // // for IE 9, use the 2D rotation 285 | // this._icon.style[L.DomUtil.TRANSFORM] = 'rotate(' + this.options.rotationAngle + 'deg)'; 286 | // } else { 287 | // // for modern browsers, prefer the 3D accelerated version 288 | this._icon.style[L.DomUtil.TRANSFORM] = 289 | this._icon.style[L.DomUtil.TRANSFORM].replace(/ rotateZ\(\d+deg\)/g, '') 290 | + ' rotateZ(' + this.options.rotationAngle + 'deg)'; 291 | // } 292 | } 293 | }, 294 | 295 | setRotationAngle: function (angle) { 296 | this.options.rotationAngle = angle; 297 | this.update(); 298 | return this; 299 | }, 300 | 301 | setRotationOrigin: function (origin) { 302 | this.options.rotationOrigin = origin; 303 | this.update(); 304 | return this; 305 | } 306 | }); 307 | })(); 308 | -------------------------------------------------------------------------------- /widgets/TimeseriesPolygonMap.js: -------------------------------------------------------------------------------- 1 | self.onInit = function () { 2 | var map = $('#TimeseriesPolygonMap'); 3 | self.ctx.map = new TbMapWidgetV2('openstreet-map', 4 | false, self.ctx, undefined, map); 5 | self.mapleaflet = self.ctx.map.map.map; 6 | window.TimeseriesPolygonMapSelf = self; 7 | //self.mapleaflet.panBy([200, 300]); 8 | self.mapleaflet.setView([32.7, 34.95], 9); 9 | // self.resizeMap(); 10 | // console.log(self); 11 | } 12 | 13 | self.resizeMap = function () { 14 | self.mapleaflet._container.style.height = (self.ctx 15 | .height - 30) + "px"; 16 | } 17 | 18 | self.clearPolygons = function () { 19 | if (!self.mapleaflet._layers) return; 20 | for (var i in self.mapleaflet._layers) { 21 | if (self.mapleaflet._layers[i]._path !== undefined) { 22 | try { 23 | self.mapleaflet.removeLayer(self.mapleaflet._layers[i]); 24 | } catch (e) { 25 | console.log("problem with " + e + self.mapleaflet._layers[i]); 26 | } 27 | 28 | } 29 | } 30 | 31 | } 32 | 33 | function parseJson(json) { 34 | var value = String(json).replace(/'/g, '\"'); 35 | return JSON.parse(value); 36 | } 37 | self.showPolygons = function (parsed, color) { 38 | try { 39 | geojsonFeature = parsed.features; 40 | } catch (err) { 41 | return; 42 | // geojsonFeature = null 43 | } 44 | var myStyle = { 45 | "color": color, 46 | "opacity": 0.8 47 | }; 48 | try { 49 | L.geoJSON(geojsonFeature, { 50 | style: myStyle 51 | }).addTo(self.mapleaflet); 52 | } catch (e) { 53 | console.log(e); 54 | } 55 | 56 | }; 57 | 58 | self.updateTimeRange = function (parsed) { 59 | var maxRange = 0; 60 | for (i = 0; i < parsed.length; i++) { 61 | // var prop = self.ctx.data[i].data[0][1]; 62 | // console.log(prop); 63 | for (j = 0; j < parsed[i].length; j++) { 64 | if (parsed[i][j].index > maxRange) { 65 | maxRange = parsed[i][j].index; 66 | } 67 | } 68 | } 69 | $('#TimeFrame')[0].max = maxRange; 70 | }; 71 | 72 | function findCurrFrame(frames, index) { 73 | if (frames instanceof Array) { 74 | for (var i = 0; i < frames.length; ++i) { 75 | if (parseInt(frames[i].index) === index) { 76 | return frames[i]; 77 | } 78 | } 79 | } 80 | return undefined; 81 | } 82 | 83 | self.getCurrIndex = function () { 84 | return parseInt($('#TimeFrame')[0].value); 85 | } 86 | self.setCurrIndex = function (val) { 87 | // console.log('setCurrIndex', val); 88 | $('#TimeFrame')[0].value = val; 89 | self.onDataUpdated(); 90 | } 91 | self.onDataUpdated = function () { 92 | try { 93 | var parsed = self.ctx.data.map(prop => 94 | parseJson( 95 | prop.data[0][1])); 96 | self.updateTimeRange(parsed); 97 | var index = self.getCurrIndex(); 98 | self.clearPolygons(); 99 | // console.log(self.ctx.data); 100 | var valueName = undefined; 101 | for (i = 0; i < self.ctx.data.length; i++) { 102 | var poly = findCurrFrame(parsed[i], index); 103 | if (!poly) continue; 104 | valueName = poly.name; 105 | var color = self.ctx.data[i].dataKey.color; 106 | self.showPolygons(poly.value, color); 107 | } 108 | $('#TimeFrameLabel')[0].value = valueName ? 109 | valueName : ''; 110 | } catch (err) { } 111 | self.ctx.map.update(); 112 | } 113 | 114 | self.setNext = function () { 115 | var maxRange = parseInt($('#TimeFrame')[0].max); 116 | if (maxRange >= 1) { 117 | self.setCurrIndex((self.getCurrIndex() + 1) % ( 118 | maxRange + 1)); 119 | } else { 120 | self.setCurrIndex(0); 121 | } 122 | } 123 | 124 | self.Play = function () { 125 | self.playing = !self.playing 126 | $('#PlayButton').text(self.playing ? 'Stop' : 127 | 'Play'); 128 | clearInterval(self.playInterval); 129 | if (self.playing) { 130 | var delay = self.ctx.settings.playDelay || 500; 131 | self.playInterval = setInterval((function () { 132 | self.setNext() 133 | }).bind(this), delay); 134 | } 135 | } 136 | 137 | self.onResize = function () { 138 | self.ctx.map.resize(); 139 | // self.resizeMap(); 140 | } 141 | 142 | self.getSettingsSchema = function () { 143 | var tbScheme = TbMapWidgetV2.settingsSchema( 144 | 'openstreet-map'); 145 | return tbScheme; 146 | } 147 | self.getDataKeySettingsSchema = function () { 148 | return TbMapWidgetV2.dataKeySettingsSchema( 149 | 'openstreet-map'); 150 | } 151 | self.actionSources = function () { 152 | return TbMapWidgetV2.actionSources(); 153 | } 154 | self.onDestroy = function () { } -------------------------------------------------------------------------------- /widgets/newWidget.js: -------------------------------------------------------------------------------- 1 | self.onInit = function () { 2 | var elemap = $('#TimeseriesPolygonImageMap'); 3 | self.ctx.map = new TbMapWidgetV2('image-map', false, self.ctx, undefined, elemap); 4 | self.posFunc = self.ctx.map.map.posFunction; 5 | setTimeout(() => { 6 | self.mapleaflet = self.ctx.map.map.map; 7 | window.TimeseriesPolygonImageMapSelf = self; 8 | // self.onResize(); 9 | // self.onDataUpdated(); 10 | 11 | console.log(self); 12 | }, 10); 13 | }; 14 | 15 | self.onDataUpdated = function () { 16 | self.drawPolygons(); 17 | self.drawWindMarkers(); 18 | }; 19 | 20 | self.drawPolygons = function() { 21 | try { 22 | const index = self.getCurrIndex(); 23 | [polygonFrames, maxRange] = self.getPolygonFrames(); 24 | $('#TimeFrame')[0].max = maxRange; 25 | $('#TimeFrameLabel')[0].value = self.showPolygonFrame(polygonFrames, index); 26 | } catch (err) { 27 | console.log("JSON error:", self.ctx.data, err); 28 | return; 29 | } 30 | }; 31 | 32 | self.clearPolygons = function () { 33 | if (!self.mapleaflet || !self.mapleaflet._layers) { 34 | return; 35 | } 36 | for (var i in self.mapleaflet._layers) { 37 | if (self.mapleaflet._layers[i]._path !== undefined) { 38 | try { 39 | self.mapleaflet.removeLayer(self.mapleaflet._layers[i]); 40 | } catch (e) { 41 | console.log("problem with " + e + self.mapleaflet._layers[i]); 42 | } 43 | } 44 | } 45 | }; 46 | 47 | function parseJson(json) { 48 | if (!json || json === '') return undefined; 49 | var value = String(json).replace(/'/g, '\"'); 50 | return JSON.parse(value); 51 | } 52 | 53 | function lerp(a, b, f) { 54 | return a + f * (b - a); 55 | } 56 | 57 | self.posOnMap = function (v) { 58 | if (self.ctx.map && self.ctx.map.map && self.ctx.map.map.imageOverlay && self.ctx.map.map.imageOverlay._bounds && self.posFunc) { 59 | const bounds = self.ctx.map.map.imageOverlay._bounds; 60 | var ne = bounds._northEast, 61 | sw = bounds._southWest; 62 | var pos = self.posFunc(v[0], v[1]); 63 | const y = lerp(ne.lat, sw.lat, pos.y); 64 | const x = lerp(sw.lng, ne.lng, pos.x); 65 | return [x, y]; 66 | } else { 67 | return v; 68 | } 69 | }; 70 | 71 | self.posFuncGeoJson = function (geojson) { 72 | geojson.forEach((g) => { 73 | g.geometry.coordinates = g.geometry.coordinates.map(shape => { 74 | return shape.map(self.posOnMap); 75 | }); 76 | }); 77 | return geojson; 78 | }; 79 | 80 | self.showPolygons = function (parsed, color) { 81 | try { 82 | var geojsonFeature = self.posFuncGeoJson(parsed.features); 83 | L.geoJSON(geojsonFeature, { 84 | style: { "color": color, "opacity": 0.8 } 85 | }).addTo(self.mapleaflet); 86 | } catch (e) { 87 | console.log(e); 88 | } 89 | }; 90 | 91 | self.getCurrIndex = function () { 92 | return parseInt($('#TimeFrame')[0].value); 93 | }; 94 | 95 | self.setCurrIndex = function (val) { 96 | $('#TimeFrame')[0].value = val; 97 | self.onDataUpdated(); 98 | }; 99 | 100 | self.getPolygonFrames = function () { 101 | var maxRange = 0; 102 | const polygonFrames = []; 103 | for (var prop in self.ctx.data) { 104 | const data = self.ctx.data[prop].data; 105 | if (!data || !data.length || typeof data[0][1] !== "string" || data[0][1][0]!=='{') continue; 106 | const frames = parseJson(data[0][1]); 107 | if (frames === undefined) continue; 108 | if (frames.length && frames[0].index) { 109 | const indices = frames.map(b => b.index); 110 | maxRange = Math.max(maxRange, Math.max.apply(this, indices)); 111 | } 112 | polygonFrames.push({ 113 | frames: frames, 114 | color: self.ctx.data[prop].dataKey.color 115 | }); 116 | } 117 | return [polygonFrames, maxRange]; 118 | }; 119 | 120 | self.showPolygonFrame = function (polygonFrames, index) { 121 | let valueName = ''; 122 | self.clearPolygons(); 123 | polygonFrames.forEach(polys => { 124 | let poly = polys.frames; 125 | if (polys.frames.length) { 126 | poly = polys.frames.find(frame => parseInt(frame.index) === index); 127 | if (poly) { 128 | valueName = poly.name; 129 | self.showPolygons(poly.value, polys.color); 130 | } 131 | } else if (poly) { 132 | self.showPolygons(poly, polys.color); 133 | } 134 | }); 135 | self.ctx.map.update(); 136 | return valueName ? valueName : ''; 137 | }; 138 | 139 | /////////////// WIND ////////////////// 140 | self.drawWindMarkers = function() { 141 | if (!self.mapleaflet) return undefined; 142 | (self.windMarkers || []).forEach(m => m.removeFrom(self.mapleaflet)); 143 | const dirs = self.getDirectionTelemetry(); 144 | console.log(dirs); 145 | self.windMarkers = dirs.map(self.showDirectionMarker); 146 | self.windMarkers = self.windMarkers.filter(a => a); 147 | }; 148 | 149 | self.getDirectionTelemetry = function () { 150 | // console.log(self.ctx.data); 151 | const nonJsons = self.ctx.data.filter(prop => prop.data.length > 0 && typeof prop.data[0][1] !== "string"); 152 | let devices = {}; 153 | nonJsons.forEach(prop => { 154 | const t = prop.datasource.name; 155 | devices[t] = devices[t] || {device: t}; 156 | const kind = prop.dataKey.name; 157 | devices[t][kind] = prop.data[0][1]; 158 | }); 159 | return Object.values(devices); 160 | }; 161 | 162 | self.showDirectionMarker = function (s) { 163 | if (s.xPos === undefined || s.yPos === undefined) return undefined; 164 | dir = 0; 165 | power = 1; 166 | if (s.u !== undefined && s.v !== undefined) { 167 | dir = Math.atan2(s.v, s.u); 168 | power = Math.sqrt(s.u * s.u + s.v * s.v); 169 | } 170 | const powerSize = power * self.ctx.settings.ArrowLenAt4; 171 | let inverseArrow = /(Win32|Win64)/i.test(navigator.platform); 172 | if (self.ctx.settings.InverseArrow) { 173 | inverseArrow = !inverseArrow; 174 | } 175 | 176 | if (inverseArrow) dir = (dir + 180) % 360; 177 | 178 | // const pos = self.posOnMap([s.latitude, s.longitude]); 179 | const pos = self.posOnMap([s.xPos, s.yPos]); 180 | var marker = L.marker([pos[1], pos[0]], { 181 | icon: L.icon({ 182 | iconUrl: self.ctx.settings.arrowImageUrl, 183 | iconSize: [powerSize, parseFloat(self.ctx.settings.ArrowWidth)] 184 | }), 185 | rotationAngle: 360 - dir % 360, // counter clockwise 186 | rotationOrigin: 'center' 187 | }).addTo(self.mapleaflet); 188 | 189 | return marker; 190 | }; 191 | 192 | self.setNext = function () { 193 | var maxRange = parseInt($('#TimeFrame')[0].max); 194 | if (maxRange >= 1) { 195 | self.setCurrIndex((self.getCurrIndex() + 1) % (maxRange + 1)); 196 | } else { 197 | self.setCurrIndex(0); 198 | } 199 | }; 200 | 201 | self.Play = function () { 202 | self.playing = !self.playing; 203 | $('#PlayButton').text(self.playing ? 'Stop' : 'Play'); 204 | if (self.playing) { 205 | var cont = () => { 206 | self.setNext(); 207 | if (self.playing) { 208 | setTimeout(cont, self.ctx.settings.playDelay); 209 | } 210 | }; 211 | cont(); 212 | } 213 | }; 214 | 215 | self.onResize = function () { 216 | self.ctx.map.resize(); 217 | self.resizeMap(); 218 | }; 219 | 220 | self.resizeMap = function () { 221 | if (!self.mapleaflet || !self.mapleaflet._container) { 222 | return; 223 | } 224 | self.mapleaflet._container.style.height = (self.ctx.height - 30) + "px"; 225 | self.mapleaflet.invalidateSize(); 226 | }; 227 | 228 | function addProp(tbScheme, name, type, props) { 229 | if (tbScheme.groupInfoes[0].GroupTitle !== 'Argos') { 230 | tbScheme.groupInfoes.forEach(inf => inf.formIndex += 1); 231 | tbScheme.groupInfoes.unshift({formIndex: 0, GroupTitle: "Argos"}); 232 | tbScheme.form.unshift([]); 233 | } 234 | if (type) { 235 | tbScheme.form[0].push({key: name, type: type}); 236 | } else { 237 | tbScheme.form[0].push(name); 238 | } 239 | tbScheme.schema.properties[name] = props; 240 | } 241 | 242 | self.getSettingsSchema = function () { 243 | var tbScheme = JSON.parse(JSON.stringify(TbMapWidgetV2.settingsSchema('image-map'))); 244 | console.log(tbScheme); 245 | addProp(tbScheme, "InverseArrow", 0, {"title": "Inverse Arrow's U and V directions","type": "boolean","default": false}); 246 | addProp(tbScheme, "playDelay", 0, {"title": "Play delay","type": "number","default": 500}); 247 | addProp(tbScheme, "ArrowLenAt4", 0, { 248 | "title": "Arrow length in pixels when power/speed is 4", 249 | "type": "number", 250 | "default": 40 251 | }); 252 | addProp(tbScheme, "ArrowWidth", 0, { 253 | "title": "Arrow width in pixels", 254 | "type": "number", 255 | "default": 40 256 | }); 257 | addProp(tbScheme, "arrowImageUrl", "image" , { 258 | "title": "Arrow image", 259 | "type": "string", 260 | "default": "data:image/png;base64,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" 261 | }); 262 | return tbScheme; 263 | }; 264 | self.getDataKeySettingsSchema = function () { 265 | return TbMapWidgetV2.dataKeySettingsSchema('image-map'); 266 | }; 267 | 268 | self.actionSources = function () { 269 | return TbMapWidgetV2.actionSources(); 270 | }; 271 | 272 | self.onDestroy = function () { }; 273 | 274 | (function () { 275 | // save these original methods before they are overwritten 276 | var proto_initIcon = L.Marker.prototype._initIcon; 277 | var proto_setPos = L.Marker.prototype._setPos; 278 | 279 | var oldIE = (L.DomUtil.TRANSFORM === 'msTransform'); 280 | 281 | L.Marker.addInitHook(function () { 282 | var iconOptions = this.options.icon && this.options.icon.options; 283 | var iconAnchor = iconOptions && this.options.icon.options.iconAnchor; 284 | if (iconAnchor) { 285 | iconAnchor = (iconAnchor[0] + 'px ' + iconAnchor[1] + 'px'); 286 | } 287 | this.options.rotationOrigin = this.options.rotationOrigin || iconAnchor || 'center bottom'; 288 | this.options.rotationAngle = this.options.rotationAngle || 0; 289 | 290 | // Ensure marker keeps rotated during dragging 291 | this.on('drag', function (e) { e.target._applyRotation(); }); 292 | }); 293 | 294 | L.Marker.include({ 295 | _initIcon: function () { 296 | proto_initIcon.call(this); 297 | }, 298 | 299 | _setPos: function (pos) { 300 | proto_setPos.call(this, pos); 301 | this._applyRotation(); 302 | }, 303 | 304 | _applyRotation: function () { 305 | if (this.options.rotationAngle) { 306 | this._icon.style[L.DomUtil.TRANSFORM + 'Origin'] = this.options.rotationOrigin; 307 | 308 | // console.log(this.options.rotationAngle); 309 | // if(oldIE) { 310 | // // for IE 9, use the 2D rotation 311 | // this._icon.style[L.DomUtil.TRANSFORM] = 'rotate(' + this.options.rotationAngle + 'deg)'; 312 | // } else { 313 | // // for modern browsers, prefer the 3D accelerated version 314 | this._icon.style[L.DomUtil.TRANSFORM] = 315 | this._icon.style[L.DomUtil.TRANSFORM].replace(/ rotateZ\(\d+deg\)/g, '') 316 | + ' rotateZ(' + this.options.rotationAngle + 'deg)'; 317 | // } 318 | } 319 | }, 320 | 321 | setRotationAngle: function (angle) { 322 | this.options.rotationAngle = angle; 323 | this.update(); 324 | return this; 325 | }, 326 | 327 | setRotationOrigin: function (origin) { 328 | this.options.rotationOrigin = origin; 329 | this.update(); 330 | return this; 331 | } 332 | }); 333 | })(); 334 | --------------------------------------------------------------------------------