diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5c6e486d84dde4fcd4c4c02db86bc0cc72152c39..f5a14f0b59ce2355c2a9ca7f46c0860aeb83f198 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -100,6 +100,7 @@ docker_build_image_all: - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh device-tilebeam latest - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh device-beamlet latest - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh device-digitalbeam latest + - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh device-antennafield latest - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh device-boot latest - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh device-docker latest - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh device-observation_control latest @@ -331,6 +332,17 @@ docker_build_image_device_ovservation_control: script: # Do not remove 'bash' or statement will be ignored by primitive docker shell - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh device-observation_control $tag +docker_build_image_device_antennafield: + extends: .base_docker_images_except + only: + refs: + - merge_requests + changes: + - docker-compose/device-antennafield.yml + - docker-compose/lofar-device-base/* + script: +# Do not remove 'bash' or statement will be ignored by primitive docker shell + - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh device-antennafield $tag docker_build_image_device_recv: extends: .base_docker_images_except only: diff --git a/CDB/LOFAR_ConfigDb.json b/CDB/LOFAR_ConfigDb.json index 2032c4de777aa785dc8bfcff70c73da0bfc197b3..56263d3c25383e5066b71e4ffdef640b8f4b348e 100644 --- a/CDB/LOFAR_ConfigDb.json +++ b/CDB/LOFAR_ConfigDb.json @@ -14,6 +14,17 @@ } } }, + "AntennaField": { + "STAT": { + "AntennaField": { + "STAT/AntennaField/1": { + "properties": { + "RECV_devices": ["STAT/RECV/1"] + } + } + } + } + }, "PDU": { "STAT": { "PDU": { diff --git a/docker-compose/device-antennafield.yml b/docker-compose/device-antennafield.yml new file mode 100644 index 0000000000000000000000000000000000000000..aadc41e0853909fe2bad7a3417209ab88537bef6 --- /dev/null +++ b/docker-compose/device-antennafield.yml @@ -0,0 +1,47 @@ +# +# Docker compose file that launches an interactive iTango session. +# +# Connect to the interactive session with 'docker attach itango'. +# Disconnect with the Docker deattach sequence: <CTRL>+<P> <CTRL>+<Q> +# +# Defines: +# - itango: iTango interactive session +# +# Requires: +# - lofar-device-base.yml +# +version: '2' + +services: + device-antennafield: + image: device-antennafield + # build explicitly, as docker-compose does not understand a local image + # being shared among services. + build: + context: .. + dockerfile: docker-compose/lofar-device-base/Dockerfile + args: + SOURCE_IMAGE: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_USER}/tango-itango:${TANGO_ITANGO_VERSION} + container_name: ${CONTAINER_NAME_PREFIX}device-antennafield + logging: + driver: "json-file" + options: + max-size: "100m" + max-file: "10" + networks: + - control + ports: + - "5715:5715" # unique port for this DS + extra_hosts: + - "host.docker.internal:host-gateway" + volumes: + - ..:/opt/lofar/tango:rw + environment: + - TANGO_HOST=${TANGO_HOST} + working_dir: /opt/lofar/tango + entrypoint: + - bin/start-ds.sh + # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA + # can't know about our Docker port forwarding + - l2ss-antennafield AntennaField STAT -v -ORBendPoint giop:tcp:0:5715 -ORBendPointPublish giop:tcp:${HOSTNAME}:5715 + restart: unless-stopped diff --git a/docker-compose/grafana/Dockerfile b/docker-compose/grafana/Dockerfile index 7eceb9c154c654da53eb0a4b060df945013bf766..14484ff1591bc4f95602addcd691e5f59461da4a 100644 --- a/docker-compose/grafana/Dockerfile +++ b/docker-compose/grafana/Dockerfile @@ -4,6 +4,7 @@ FROM grafana/grafana RUN grafana-cli plugins install briangann-datatable-panel RUN grafana-cli plugins install ae3e-plotly-panel RUN grafana-cli plugins install yesoreyeram-infinity-datasource +RUN grafana-cli plugins install aceiot-svg-panel COPY grafana.ini /etc/grafana/ diff --git a/docker-compose/grafana/dashboards/svg.json b/docker-compose/grafana/dashboards/svg.json new file mode 100755 index 0000000000000000000000000000000000000000..8c762495cf5a5124fed3ed5ab3da141c5cdfd390 --- /dev/null +++ b/docker-compose/grafana/dashboards/svg.json @@ -0,0 +1,165 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 6, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Mixed --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "light-green", + "value": null + }, + { + "color": "red", + "value": 1 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 21, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 4, + "options": { + "addAllIDs": false, + "captureMappings": false, + "eventAutoComplete": false, + "eventSource": "// find the right data series\nlet series = data.series.find(\n x => x.refId == \"B\"\n && x.fields[1].labels.device == \"total\"\n)\n\n// use the last value\nlet buffer = series.fields[1].values.buffer\nlet lastValue = buffer[buffer.length-1]\n\n// colour Drenthe accordingly\nsvgmap.Drenthe.css('fill', lastValue > 1 ? '#f00' : '#0f0')\nsvgmap.Drenthe.linkTo(function(link) {\n link.to('http://www.drenthe.nl').target('_blank')\n})\n\n// lookup an alert\nalert = get_alert(data, \"test\")\n\n// colour Groningen accordingly\nsvgmap.Groningen.css('fill', alert.colour)\nif (alert.href) {\n svgmap.Groningen.linkTo(function(link) {\n link.to(alert.href).target('_blank')\n })\n}\n\nconsole.log(\"refreshed\")", + "initAutoComplete": false, + "initSource": "// Lookup an alert in Grafana\r\nget_grafana_alert = (data, name) => {\r\n series = data.series.find(\r\n x => x.refId == \"GrafanaAlerts\"\r\n )\r\n\r\n return series.meta.custom.data.find(\r\n x => x.labels.alertname == name\r\n )\r\n}\r\n\r\n// Lookup an alert in Alerta\r\nget_alerta_alert = (data, name) => {\r\n series = data.series.find(\r\n x => x.refId == \"AlertaAlerts\"\r\n )\r\n\r\n return series.meta.custom.data.alerts.find(\r\n x => x.event == name\r\n )\r\n}\r\n\r\n// Return everything about an alert\r\nget_alert = (data, name) => {\r\n let grafana_alert = get_grafana_alert(data, name)\r\n let alerta_alert = get_alerta_alert(data, name)\r\n\r\n if (alerta_alert) {\r\n href = alerta_alert.href\r\n\r\n if (grafana_alert)\r\n colour = 'red'\r\n else\r\n colour = 'orange'\r\n } else if (grafana_alert) {\r\n // firing\r\n colour = 'red'\r\n\r\n href = '/alerting/grafana/'+ grafana_alert.labels.__alert_rule_uid__ +'/view'\r\n } else {\r\n colour = 'green'\r\n href = undefined\r\n }\r\n\r\n return {\r\n name: name,\r\n alerta_alert: alerta_alert,\r\n grafana_alert: grafana_alert,\r\n colour: colour,\r\n href: href\r\n }\r\n}", + "svgMappings": [ + { + "mappedName": "Drenthe", + "svgId": "NL-DR" + }, + { + "mappedName": "Groningen", + "svgId": "NL-GR" + }, + { + "mappedName": "Friesland", + "svgId": "NL-FR" + } + ], + "svgSource": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- (c) ammap.com | SVG map of Netherlands - Low -->\n<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:amcharts=\"http://amcharts.com/ammap\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" version=\"1.1\">\n\t<defs>\n\t\t<style type=\"text/css\">\n\t\t\t.land\n\t\t\t{\n\t\t\t\tfill: #CCCCCC;\n\t\t\t\tfill-opacity: 1;\n\t\t\t\tstroke:white;\n\t\t\t\tstroke-opacity: 1;\n\t\t\t\tstroke-width:0.5;\n\t\t\t}\n\t\t</style>\n\n\t\t<amcharts:ammap projection=\"mercator\" leftLongitude=\"3.359304\" topLatitude=\"53.560347\" rightLongitude=\"7.227792\" bottomLatitude=\"50.750938\"></amcharts:ammap>\n\n\t\t<!-- All areas are listed in the line below. You can use this list in your script. -->\n\t\t<!--{id:\"NL-DR\"},{id:\"NL-FL\"},{id:\"NL-FR\"},{id:\"NL-GE\"},{id:\"NL-GR\"},{id:\"NL-LI\"},{id:\"NL-NB\"},{id:\"NL-NH\"},{id:\"NL-OV\"},{id:\"NL-UT\"},{id:\"NL-ZE\"},{id:\"NL-ZH\"}-->\n\n\t</defs>\n\t<g>\n\t\t<path id=\"NL-DR\" title=\"Drenthe\" class=\"land\" d=\"M467.85,123.65L472.18,125.25L474.19,119.96L475.36,119.6L475.73,116.23L478.42,108.02L481.8,104.33L482.29,101.78L487.32,99.04L488.72,97.7L488.81,96.77L494.16,95.38L496.91,95.06L502.31,96.99L503.31,99.57L503.91,99.59L506.25,103.68L507.39,106.86L509.78,106.05L510.96,110.15L512.28,110.15L515.82,113.26L516.88,116.18L518.62,118.31L518.66,120.12L527.91,116.41L534.48,116.76L535.73,117.6L565.51,149.53L578.22,168.14L579.75,169.47L583.09,170.36L578.6,181.27L582.36,181.67L582.85,183.52L583.44,183.47L585.78,186.9L586.81,187.94L588.06,187.81L587.52,190.53L590.7,190.37L590.7,190.37L587.39,197.87L584.74,241.37L582.54,243.97L577.84,242.8L576.62,241.63L574.7,241.76L572,240.38L568.61,242.46L565.94,242.72L562.99,241.84L562.54,240.25L559.61,239.24L557.11,239.5L555.98,238.56L554.29,239.6L552.49,239.78L550.14,238.9L547.87,240.04L541.98,238.74L539.89,239.47L539.37,240.3L537.93,239.78L535.33,240.56L530.46,245.61L530.46,245.61L528.64,245.5L530.16,239.76L515.13,233.3L505.03,235.57L499,240.2L499.94,249.98L497.72,247.9L493.23,247.58L491.72,245.89L490.6,248.36L487.93,248.73L486.45,247.45L485.9,245.43L483.94,247.9L482.51,247.17L482.01,248.08L480.5,248.03L478.68,249.46L477.34,247.9L476.94,245.19L475.16,241.37L469.32,237.15L469.7,235.46L469.16,234.53L465.04,235.59L463.94,236.5L462.51,234.92L460.8,235.8L459.46,234.55L458.33,234.47L456.56,232.36L453.87,231.64L452.28,230.15L450.17,230.65L449.32,231.82L446.91,231.14L445.69,231.64L442.69,228.07L436.89,213.11L437.95,211.62L444.66,209.2L444.85,207.94L450.01,202.23L439.24,189.9L436.89,186.27L436.89,186.27L450.2,176.85L454.41,170.99L458.3,167.25L465.8,167.8L470.77,172.9L480.14,166.2L485.66,155.75L475.31,139.67L476,130.74L466.56,127.17z\"/>\n\t\t<path id=\"NL-FL\" title=\"Flevoland\" class=\"land\" d=\"M327.09,271.35l-0.68,-0.75l2.77,-7.32l3.85,-2.49l0.3,-1.12l-1.82,-5.61l-8.78,-14.14l0,0l0.36,-0.26l0,0l8.85,14.14l1.87,6.05l-0.4,1.27l-3.91,2.44l-2.1,5.11L327.09,271.35zM372.13,190.84l1.58,0.31l1.25,1.23l1.08,-0.44l-0.98,-2.95l3.17,0.97l5.73,5.8l0.98,3.79l0.59,-0.34l0,0l3.58,4.88h2.9l3.59,1.25l10.22,8.35l3.29,9.31l1.11,0.57l2.06,3.75l-0.38,1.59l-2.88,2.16l-1.79,2.03l0.06,0.89l5.81,1.59l2.52,2.21l2.58,0.7l2.28,2.52l-0.79,1.35l-3.56,2.37l-5.24,1.14l-3.88,2.63l-2.34,0.29l-2.06,-1.35l-9.32,2.05l-1.25,1.04l-9.38,-0.03l-0.06,6.86l5.19,0.21l6.74,7.19l1.74,8.62l0,0l-3.47,12l-1.85,0.16l-2.21,4.22l0.85,1.61l-2.07,3.55l-3.08,3.03l-7.85,6.37l-1.35,-0.49l0.17,0.85l-2.33,2.12l-3.07,1.99l-2.96,0.41l-4.32,3.7l-1.33,-0.98l-5,2.74l-1.27,-0.98l-2.69,0.88l-1.85,2.87l-6.88,19.91l-19.92,5.93l0,0l-11.11,-10.58l0,0l-3.81,-3.77l-4.65,-3.07l-9.73,-2.45l-11,1.88l-2.23,-2.07l0,0l1.03,-1.68l-2.12,-1.4l0.66,-1.24l-0.27,-5.01l-1.63,-3.1l0.03,-1.53l5.41,-3.26l0.81,-1.86l7.18,-4.24l2.59,-0.44l1.46,-2.04l23.64,-16.37l2.58,-0.8l6.12,-5.5l0.3,-0.99l-0.6,-2.1l-0.98,0.21l0.14,-1.38l0.74,0.23l1.66,-1.3l2.44,-4.62l5.48,-0.57l5.36,-5.12l-0.48,-0.96l0.55,-0.55l0.52,0.86l4.48,-4.6l10.59,-3.07l3.51,-2.89l-5,-8.06l-2.83,-2.08l0.52,-0.44l-0.74,-0.96l-1,0.18l0.27,-26.71l10.57,-17.2L372.13,190.84z\"/>\n\t\t<path id=\"NL-FR\" title=\"Friesland\" class=\"land\" d=\"M317.98,128.19l2.55,1.68l1.38,3.55l-3.05,-0.21l-1.38,-2.44l-0.35,-2.34L317.98,128.19zM312.42,128.35l0.06,1.1l-5.74,1.26l-12.99,11.45l-0.08,0.68l-1.66,0.79l1.41,-0.03l-1.58,0.21l-5.62,4.88l0,0l-0.65,-0.87l0,0l5.51,-4.83l-0.09,-0.58l0.73,-0.39l-0.49,0.84l14.79,-13.05l4.78,-1.66L312.42,128.35zM270.93,66.85l4.19,0.79l0.41,1.32l-1.22,1.14l-0.71,-0.32l0.3,0.55l-1.91,-0.32l-2.36,0.66l-3.17,2.59l-11.3,4.67l0.74,3.11l-1.47,0.87l0.05,2.56l-3.86,1.58l-2.15,2.32l-4.45,0.53l-2.9,1.58l-1.31,-0.37l-0.76,-2.24l8.96,-5.67l7.12,-7.1l9.3,-6.34L270.93,66.85zM282.06,71.45l-2.01,1.93l-1.96,-0.05l0.92,-0.98l-1.09,-0.95l4.04,-4.12l1.49,-0.4l0.36,1.74L282.06,71.45zM448.11,39.72l-3.69,5.38l0.93,5.74l1.68,1.8l5.9,1.8l1.72,3.41l2.96,-1.43l5.52,1.9l-0.84,7.32l1.96,1.22l-2.31,1.58l-1.23,3.38l-1.47,-0.19l-1.17,0.69l0.17,5.52l-0.82,0.4l-0.71,-0.5l-1.28,2.74l-0.96,0.37l0.46,0.66l-0.81,0.98l0.16,2.03l-2.74,0.45l2.67,4.88L454,91.03l-1.49,2.79l-1.6,1.74l-1.42,0.42l-0.22,1.4l-3.32,5.58l-0.7,9.42l4.83,5.29l5.21,0.79l4.37,-0.18l2.31,1.24l2.09,2.6l3.8,1.52l0,0l-1.28,3.52l9.43,3.57l-0.68,8.93l10.35,16.08l-5.52,10.45l-9.37,6.7l-4.97,-5.1l-7.5,-0.55l-3.89,3.74l-4.21,5.86l-13.31,9.41l0,0l-6.11,4.02l-2.77,-0.1l-1.79,0.73l0.21,3.19l-3.73,2.01l-0.71,-1.44l-4.13,1.12l-2.09,-0.99l-2.15,-5.38l-2.83,0.94l-0.02,1.38l-1.47,-0.6l-2.41,0.52l-0.71,0.86l0.02,2.04l-4.21,3.97l-3.31,1.57l-5.87,-1.57l-1,0.57l-0.35,-1.07l-2.17,-1.49l-3.78,2.64l0,0l-0.59,0.34l-0.98,-3.79l-5.73,-5.8l-3.17,-0.97l0.98,2.95l-1.08,0.44l-1.25,-1.23l-1.58,-0.31l0,0l0.11,-1.12l-1.46,0.57l-0.7,-1.49l-3.99,-0.21l0.11,-0.99l-0.92,1.7l-1.68,-0.29l-0.73,-0.84l0.3,-2.09l-1.77,-1.31l-4.23,2.46l-2.21,-0.31l-2.83,0.65l-1.06,2.77l-4.15,1.57l-14.86,-5.7l-5.43,1.65l-4.35,-2.2l-4.46,-4.1l-2.5,-3.64l2.79,-3.4l4.53,-2.12l0.87,-1.23l0.16,-2.67l-0.84,-4.14l-0.87,-1.05l1.79,-0.86l-0.6,-0.42l1.08,0.03l0.43,-1.96l-0.55,-1.39l-0.76,0.03l1.17,-0.29l0.16,-1.21l-1.72,-5.4l-0.57,-4.43l1.38,-2.33l0.05,-2.7l-1.39,-0.39l-0.25,-1.44l0.73,-0.29l0.11,-1.1l0.43,0.31l-0.21,-3.94l-0.55,3.81l-0.6,0.42L321,137.2l-1.55,-1.71l-0.25,-1.55l1.22,-0.21l0.82,0.71v-0.68l0.67,-0.05l0.49,0.79l0.73,-0.1l-1.77,-3.44l-0.3,-3.13l-1.71,-3.26l-5.93,4.89l-0.92,-1.52l2.07,-0.74l4.42,-4.28l0.51,-3.44l2.56,-1.55l1.35,-1.76l1.83,-11.34l-0.05,-1.11l-1.06,-1.05l1.9,-1.76l2.6,-7.96l1.61,-2.58l5.49,-5.64l7.14,-4.46l4.42,-4.54l0.96,-2.69l4.4,-4.44l4.46,-2.3l11.87,-3.73l11.6,-7.03l16.19,-7.46l3.31,-1.91l-1.6,-2.7l0.44,0.61l0.35,-0.37l0.98,2.28l4.56,-0.03l8.61,-2.97l0.43,0.45l2.36,-0.42l4.21,-1.35l8.36,-1.09l2.55,0.69l0.28,-0.69l0.32,0.69l0.67,-1.03l6.95,1.46l6.28,-2.97l1.06,-0.08L448.11,39.72zM342.85,30.28l-2.01,0.48l5.13,-0.19l-0.11,2.54l-3.18,1.62l-2.07,-0.13l-4.62,2.86l-2.31,-0.98l0.29,2.25l-3.67,-1.46l2.03,1.93l2.45,0.93l-1.2,0.16l-0.49,0.95l-10.11,0.26l-2.47,0.64l-6.12,4.87l-3.29,1.51l-1.87,-0.69l-0.38,0.74L307,49.06l-0.6,1.27l-5.85,-0.58l-2.47,2.17l-0.74,-0.56l-2.61,0.32l-0.66,0.9l0.09,1.9l-1.8,-0.4l-3.45,3.31l-1.87,-0.45l-2.66,-1.93l-0.44,-3.28l3.4,-3.33l2.18,-3.89l0.28,0.77l0.66,-2.22l19.88,-3.89l1.25,0.27l15.4,-5.01l13.39,-5.46l3.12,-0.58l2.71,0.53l-0.33,0.95L342.85,30.28zM427.56,26.92l0.27,2.12l-0.4,-0.69l-2.17,1.86l-0.87,-0.42l-0.33,-1.48l0.92,-1.35L427.56,26.92zM374.01,26.86l19.15,-1.56l19.37,0.05l1.74,0.85l-0.9,0.95l-1.6,0.34l-8.45,-0.21l-2.96,1.64l-13.34,3.66l-4.64,-0.03l-0.41,1.27l-0.02,-1.7l-7.67,-0.82l-3.31,3.31l-5.71,1.54l-0.49,1.25l-5.44,-2.83l-2.59,-3.6l0.63,-4.93l2.39,-2.09l10.44,2.63L374.01,26.86zM441.64,13.81l1.42,0.27l1.55,1.46l4.51,0.48l18.02,-2.2l5.78,-0.24l0.29,0.45l-2.77,1.96l-8.67,3l-1.06,1.33l-3.96,1.86l-7.82,1.14l0.74,1.25l-1.12,-1.11h-4.23l-0.32,1.06l-0.16,-1.01l-1.08,0.21l-0.71,0.88l0.41,0.56l-1.77,0.16l0.57,2.02l-3.21,-0.27l-1.65,1.59l-0.68,-0.48l0.66,-3.34l1.66,-4.03l1.42,-2.07l3.88,-3.08l-6.19,-1.75l-1.17,2.97l-1.03,-0.16l0.11,-0.96l1.09,-2.52L441.64,13.81z\"/>\n\t\t<path id=\"NL-GE\" title=\"Gelderland\" class=\"land\" d=\"M396.24,273.37L398.39,272.57L400.85,274.75L404.06,279.26L406.21,284.37L407.41,283.82L408.03,284.39L410.97,282.66L412.34,284.63L419.23,277.5L420.94,276.9L422.05,275.58L426.69,278.59L428.51,282.27L432.16,284.37L433.51,286L433.97,292.53L438.18,297.73L439.17,301.28L437.2,309.24L433.89,310.82L430.75,311.42L430.12,312.29L430.38,313.82L432.72,315.37L429.85,317.26L429.84,322.4L428.93,325.52L430.5,325.73L431.13,329.73L433.84,330.09L434.17,330.69L435.44,333.29L435.57,335.79L438.41,340.39L437.5,342.71L440.19,343.76L442.15,343.58L441.96,345L440.52,346.57L440.95,348.22L443.37,349.33L444.09,351.21L444.41,350.44L443.54,348.92L444.16,349.18L444.32,348.02L445.04,348.79L445.63,346.26L449.76,346.29L452.07,348.02L457.12,349.07L460.74,349.33L460.82,348.58L461.86,348.94L464.06,347.94L465.27,349.33L468.54,349.36L471.2,348.94L471.29,346.75L473.29,345.26L478.04,344.33L483.56,344.95L483.51,346.24L485.27,349.67L488.46,353.01L489.76,355.64L492.06,356.36L493,359.74L495.58,361.69L499.14,360.79L505.43,361.82L511.23,360.4L513.75,362.62L513.35,364.09L514.68,365.32L517.33,362.95L518.94,364.29L519.72,362.93L520.38,362.98L523.64,364.7L524.37,365.96L523.83,367.33L524.24,371.34L522.56,374.04L531.05,377.02L538.18,376.97L538.18,376.97L537.5,382.29L536.95,383.16L536.41,382.65L535.49,383.52L536.41,385.63L534.45,388.07L527.72,389.2L527.98,390.64L526.38,393.95L526.6,397.23L530.92,397.23L537.07,400.18L539.13,402.6L545.59,407.44L545.89,408.72L548.75,409.16L549.32,415.75L548.86,416.56L546.76,416.64L544.32,417.97L543.91,423.43L540.1,428.6L537.2,429.8L536.85,431.03L534.51,431.85L533.75,433.25L532.26,433.97L529.64,431.9L527.75,429.03L525.43,428.78L519.13,431.77L518.12,432.82L516.77,432.59L509.45,435.04L506.38,437.47L505.22,436.55L504.02,437.04L497.99,441.1L496.83,443.23L492.26,444.89L489.11,441.84L486.31,443.51L485.52,441.67L479.98,439.52L479.25,442.43L482.93,445.09L481.72,446.78L482.42,449.23L481.74,449.25L482.26,451.02L481.61,451.6L478.95,449.84L475.49,449.31L475.11,447.11L473.97,446.09L466.33,445.75L465.3,441.08L462.26,439.62L461.06,439.44L458.4,441.03L451.48,440.92L447.31,437.5L447.2,436.48L448.41,436.25L448.07,435.25L444.68,432.67L444.47,433.15L442.59,431.97L438.98,432.59L437.91,433.41L436.74,432.49L434.62,434.92L437.07,434.97L439.85,436.68L440.82,440.52L443.9,443L444.19,448.05L438.43,446.32L433.67,445.96L428.08,441.97L427.21,443.58L426.18,444.02L426.4,445.42L423.19,447.7L422.67,447.16L420.8,447.85L419.99,449.46L418.13,450.4L415.83,450.66L411.83,449.18L409.27,452.35L411.37,453.98L410.86,454.64L409.6,454.34L409.41,455.18L412.1,456.63L414.31,458.93L413.57,461.2L414.08,462.25L416.43,462.81L415.39,463.86L415.22,465.21L416.31,466.46L416.23,467.2L410.86,470.95L410.52,472.65L410.52,472.65L407.59,473.44L406.24,471.71L404.44,471.15L403.92,468.29L400.93,464.47L396.65,464.72L397.45,468.45L396.56,469.39L396.56,469.39L394.19,468.96L383.22,470.74L379.82,470.49L377.59,469.06L375.31,465.62L371.56,464.7L368.47,460.87L365.51,460.36L363.82,459.29L360.75,453.75L357.96,453.57L352.4,450.99L347.68,451.58L345.8,454.13L344.78,454.44L341.49,453.62L339.06,451.2L337.49,450.69L335.95,451.35L334.47,454.87L331.62,455.97L328.39,455.92L326.81,455.26L324.85,452.86L323.55,452.88L317.65,461.79L315.91,469.47L306.93,474.54L302.31,473.67L298.96,475.41L294.12,472.91L290.4,473.72L285.21,473.04L279.62,474.46L279.56,472.6L282.09,470.03L281.06,468.73L281.49,465.62L277.88,463.86L275.49,461.79L273.61,462.04L272.07,463.68L270.38,463.6L267.15,458.96L262.91,457.02L259.91,453.21L259.91,453.21L262.28,453.26L263.31,448.28L259.91,447.82L258.96,446.24L259.3,444.83L258.72,442.97L259.13,442.54L260.93,444.12L264.94,444.02L266.47,444.63L269.37,442.77L268.28,442.1L268.13,440.36L272.45,439.08L274.12,435.91L277.79,436.35L278.44,433.48L280.63,429.42L281.38,429.77L281.28,428.16L288.21,415.77L288.21,415.77L290.38,417.18L293.26,417.41L296.46,412.88L297.66,412.57L302.52,416.18L307.09,417.05L309.54,418.69L310.89,418.77L312.82,417.79L315.64,414.9L320.79,415.08L325.76,413.18L328.88,410.85L333.2,411.7L336.9,411.62L340.1,413.88L345.21,415.77L347.49,418.49L352.82,420.07L355.5,421.87L357.09,420.46L358,420.97L357.93,419.66L358.87,419.43L358.16,415.54L358.74,413.82L357.14,411.93L356.11,408.77L353.85,407.39L352.79,404.62L350.37,402.8L351.24,402.78L351.45,398.29L348.44,397.16L348.65,395.39L347.44,394.87L348.68,387.33L348.42,385.32L347.46,384.68L347.33,382.83L346.73,382.42L346.76,380.23L345.43,380.95L345.02,380.47L343.61,380.83L344.94,384.27L344.83,384.78L343.55,384.4L343.04,385.3L344.09,385.73L342.57,387.35L339.85,387.17L339.44,388.56L336.24,389.05L332.49,386.45L334.2,384.27L336.48,382.83L336.27,380.16L337.73,378.33L336.62,377.61L339.89,377.95L339.26,376.35L337.76,375.2L338.34,373.83L340.07,373.58L340.84,372.09L338.71,371.03L338.9,369.15L335.99,368.54L336.21,366.81L334.75,365.29L326.19,363.57L325.54,360.58L328.06,359.86L328.42,356.36L321.95,351.08L323.77,343.25L323.77,343.25L343.69,337.32L350.58,317.41L352.43,314.54L355.12,313.66L356.38,314.65L361.38,311.91L362.71,312.89L367.03,309.19L369.99,308.78L373.06,306.79L375.39,304.67L375.21,303.81L376.56,304.3L384.41,297.94L387.49,294.91L389.57,291.36L388.71,289.75L390.93,285.53L392.78,285.38z\"/>\n\t\t<path id=\"NL-GR\" title=\"Groningen\" class=\"land\" d=\"M542.88,27.47l1.39,0.34l2.55,-2.07l2.75,1.11l0.33,0.93l-0.4,1.3l-2.75,-0.56l-0.11,0.5l2.47,0.61l-0.17,0.72l-1.44,0.19l4.35,0.69l0.11,-0.53l-1.54,-0.53l0.67,-2.54l3.1,0.98l3.5,3.34l-1.33,4.77l-0.17,3.71l2.29,3.31l-0.43,0.69l0.78,0.71l-0.21,2.78l1.74,7.91l5.67,4.1l-0.93,1.85l10.13,3.81l2.22,1.66l6.06,1.69l4.68,-0.11l0.66,-0.95l2.82,-1l0.03,0.69l-2.33,2.14l-0.05,4.23l-1.27,3.51l1.19,2.56l2.45,1.66l5.47,0.4l1.61,1.19l1.87,0.5l1.09,-0.42l1.47,1.11l5.41,1.66l0.55,-2.03l1.34,11.63l-1.49,2.61l2.96,2.74l-3.78,1.16l-2.15,4.69l-0.06,2.53l-1.55,2.21l0.63,4.39l2.85,2.1l-0.33,8.75l2.06,17.36l0.81,2.07l-1.28,1.6l-4.16,15.26l-12.14,20.13l-2.63,3.37l-0.35,0.76l0.79,2.95l0,0l-3.18,0.16l0.54,-2.72l-1.25,0.13l-1.03,-1.05l-2.34,-3.42l-0.59,0.05l-0.49,-1.86l-3.77,-0.39l4.49,-10.91l-3.34,-0.89l-1.54,-1.33l-12.71,-18.6l-29.78,-31.94l-1.25,-0.84l-6.57,-0.34l-9.26,3.71l-0.03,-1.81l-1.74,-2.13l-1.06,-2.92l-3.54,-3.1h-1.31l-1.19,-4.11l-2.39,0.82l-1.14,-3.19l-2.34,-4.08l-0.6,-0.03l-1,-2.58l-5.4,-1.92l-2.75,0.32l-5.35,1.4l-0.09,0.92l-1.39,1.34l-5.03,2.74l-0.49,2.55l-3.39,3.69l-2.69,8.21l-0.36,3.37l-1.17,0.37l-2.01,5.28l-4.34,-1.6l0,0l-3.8,-1.52l-2.09,-2.6l-2.31,-1.24l-4.37,0.18l-5.21,-0.79l-4.83,-5.29l0.7,-9.42l3.32,-5.58l0.22,-1.4l1.42,-0.42l1.6,-1.74l1.49,-2.79l0.22,-1.19l-2.67,-4.88l2.74,-0.45l-0.16,-2.03l0.81,-0.98l-0.46,-0.66l0.96,-0.37l1.28,-2.74l0.71,0.5l0.82,-0.4l-0.17,-5.52l1.17,-0.69l1.47,0.19l1.23,-3.38l2.31,-1.58l-1.96,-1.22l0.84,-7.32l-5.52,-1.9l-2.96,1.43l-1.72,-3.41l-5.9,-1.8l-1.68,-1.8l-0.93,-5.74l3.69,-5.38l0,0l1.39,-0.4l0.05,1.01l0.63,0.03l7.55,-1.88l6.52,3.31l2.36,2.52l4.1,-3.04l9.29,-3.92l26.32,-3.36l21.9,-8.03l8.85,-0.88L542.88,27.47zM482.21,10.71l0.74,0.13l-0.08,0.66l-4.07,-0.61l1.17,-0.53L482.21,10.71zM510.46,9.17l1.11,0.19l-0.66,1.25l-2.55,-1.35l1.2,-0.56L510.46,9.17zM515.16,4.65l-0.11,0.53l-2.23,-0.29l0.79,1.94l-3.24,-1.54l-2.56,0.24l-0.38,-0.5l1,-1.54l1.01,-0.29l3.42,0.37L515.16,4.65zM495.68,4.01l2.56,0.58l2.67,2.63l-3.21,-0.48l-1.01,-0.98l-0.33,2.07L494,7.57l-0.85,0.58l0.49,1.27l-0.78,0.53l-1.39,-0.4l-0.71,-0.58l-0.35,-2.1l-2.52,-2.31l0.78,-4.46l0.6,0.08l0.68,2.31L495.68,4.01z\"/>\n\t\t<path id=\"NL-LI\" title=\"Limburg\" class=\"land\" d=\"M396.56,469.39L397.45,468.45L396.65,464.72L400.93,464.47L403.92,468.29L404.44,471.15L406.24,471.71L407.59,473.44L410.52,472.65L410.52,472.65L410.8,474.08L416.83,474.23L421.99,476.99L423.41,479.13L424.84,479.64L424.63,480.78L423.02,480.78L422.04,482L423.05,485.21L422.34,487.02L422.99,490.56L430.36,493.92L431.31,493.59L431.67,494.3L436.31,495.11L434.52,501.32L433.32,504.19L432.72,503.91L432.29,507.93L437.04,511.64L438.03,513.54L442.31,517.68L446.04,525.1L449.43,527.76L451.69,531.72L451.66,537.01L453.27,541.96L452.58,548.31L451.75,548.67L451.69,553.25L450.5,560.21L453.64,560.06L451.77,563.55L453.64,570L448.02,575.18L448.41,576.67L444.58,577.5L442.29,583.74L438.35,589.19L437.35,592.12L431.34,598.75L429.24,600.04L431.48,604.82L428.63,605.86L429.63,615.08L431.04,617.82L433.73,618.22L437.34,615.4L444.11,612.13L446.74,614.17L440.04,617.49L445.61,621.29L443.67,622.63L443.59,623.68L438.18,624.49L434.79,626.7L432.38,627.13L431.5,629.82L430.68,629.34L429.46,631.15L426.99,632.18L426.78,633.46L424.82,635.73L422.99,637.13L421.23,637.66L420.53,637.23L419.58,637.94L417.08,640.33L416.13,642.23L414.66,642.76L414.92,643.57L413.21,645.65L412.89,649.72L411.12,650.54L411.42,651.95L407.89,652.1L404.23,644.27L400.71,647.53L396.53,648.11L396.81,649.67L398.7,651.98L397.81,653.73L398.82,656.29L401.43,658.25L401.39,659.73L402.86,660.38L402.64,663.94L401.01,665.75L401.75,667.2L402.89,667.27L407.49,664.62L410.61,663.92L412.73,666.15L415.03,665.17L421.89,664.94L420.31,670.06L418.74,671.61L420.56,672.74L421.02,677.35L425.2,678.85L426.05,678.55L426.74,679.2L426.56,680.43L429.79,680.2L432.04,681.6L432.97,680.58L430.93,683.63L429.85,687.73L432.05,692.66L430.15,695.94L429.68,699.31L427.1,698.01L426.78,696.96L420.96,699.49L420.85,702.96L422.46,705.96L421.62,706.41L421.89,707.18L419.2,710.2L418.44,710.4L415.48,708.53L413.92,710.35L413.79,711.85L415.82,712.45L417.7,714.82L422.1,717.52L420.53,720.44L421.21,722.06L414.93,722.48L413.57,720.56L411.88,720.16L404.83,723.11L404.39,722.01L403.03,722.16L402.33,723.01L400.4,721.61L399.82,718.52L396.05,720.46L394.39,722.18L393.43,719.69L391.34,721.04L387.67,721.81L385.09,718.17L383.71,718.74L382.37,715.2L380.78,715.47L376.7,720.76L374.9,720.76L373.57,719.84L372.73,720.89L370.07,722.03L367.71,721.39L370.61,715.32L369.17,711.85L370.21,709.38L369.31,707.96L367.19,707.78L366.94,706.78L363.46,705.86L363.33,704.71L362.32,704.13L362.62,702.81L360.88,699.44L361.08,694.91L362.22,693.94L361.62,693.19L362.03,692.64L365.61,690.74L366.94,690.74L367.51,689.06L369.45,687.01L370.29,683.4L371.49,683.15L373.14,684.1L373.96,683.68L374.91,679.52L376.81,677.12L377.7,674.32L379.42,673.46L378.43,671.03L377.32,670.83L374.85,672.24L373.77,671.66L373.63,670.38L380.8,661.48L380.81,658.45L382.37,655.11L379.5,652.43L381.62,648.14L381.99,645.27L384.77,646.28L386.12,645.57L385.59,642.74L386.91,640.98L385.44,638.01L387.57,636.81L389.9,637.74L391.15,636.38L391.37,634.95L390.72,633.99L387.59,633.24L387.62,631.35L389.87,628.59L392.51,628.06L393.24,625.77L394.79,624.69L391.84,622.4L392.05,621.52L389.99,618.7L388.39,620.89L386.86,620.03L382.59,622.75L382.21,621.82L382.84,620.01L381.59,619.48L381.42,618.73L382.59,617.74L381.81,617.57L382.21,616.21L380.74,614.7L377.38,613.49L376.61,614.37L373.14,614.55L371.91,615.5L368.73,614.4L366.78,614.97L365.96,614.22L363.84,614.34L362.84,612.73L362.81,611.15L348.91,605.28L348.91,605.28L353.53,603.87L353.5,604.42L357.52,602.4L358.49,591.89L365.53,581.87L397.67,571.84L406.96,563.88L397.37,547.53L395.51,536.96L394.3,533.87L391.97,517.81L400.37,519.46L403.03,521.57L408.24,521.03L418.65,517.18L422.54,521.16L423.46,520.27L425.09,520.12L423.95,517.94L423.93,513.67L421.8,510.06L421.17,504.24L417.19,500.35L413.25,497.71L413,496.44L414.23,492.85L410.93,491.81L412.11,489.01L410.67,481.55L407.78,480.02L403.4,479.43L400.75,478.36L399.65,476.96L398.58,471.3z\"/>\n\t\t<path id=\"NL-NB\" title=\"Noord-Brabant\" class=\"land\" d=\"M259.91,453.21l3.01,3.8l4.24,1.94l3.23,4.64l1.69,0.08l1.54,-1.63l1.88,-0.25l2.39,2.07l3.61,1.76l-0.43,3.11l1.03,1.3l-2.53,2.57l0.06,1.86l5.59,-1.43l5.19,0.69l3.72,-0.82l4.84,2.5l3.35,-1.73l4.62,0.87l8.97,-5.07l1.74,-7.68l5.9,-8.91l1.3,-0.03l1.96,2.4l1.58,0.66l3.23,0.05l2.85,-1.1l1.49,-3.52l1.54,-0.66l1.57,0.51l2.44,2.43l3.29,0.82l1.01,-0.31l1.88,-2.55l4.72,-0.59l5.57,2.58l2.79,0.18l3.07,5.54l1.69,1.07l2.96,0.51l3.08,3.83l3.75,0.92l2.28,3.44l2.23,1.43l3.4,0.26l10.97,-1.79l2.37,0.43l0,0l2.03,1.91l1.06,5.66l1.11,1.4l2.64,1.07l4.38,0.59l2.9,1.53l1.44,7.46l-1.19,2.8l3.31,1.04l-1.23,3.59l0.25,1.27l3.94,2.64l3.97,3.89l0.63,5.82l2.14,3.61l0.02,4.27l1.14,2.18l-1.63,0.15l-0.92,0.89l-3.89,-3.99l-10.41,3.86l-5.21,0.53l-2.66,-2.11l-8.4,-1.65l2.33,16.06l1.22,3.09l1.85,10.57l9.59,16.35l-9.29,7.96l-32.14,10.03l-7.04,10.02l-0.96,10.52l-4.02,2.02l0.03,-0.55l-4.62,1.41l0,0l-0.6,-0.53l-0.71,-4.92l0.14,-5.77l-4.62,-4.31l-1.76,-3.08l-4.05,-1.14l-0.95,-0.08l-2.86,3.79l-3.85,0.73l-0.49,1.49l-0.89,0.1l-2.6,3.46l-11.31,-3.03l-1.57,3.08l-6.73,0.35l-4.78,-1.21l-4.41,1.01l-1.44,-1.39l2.14,-9.51l-6.19,-4.01l-4.13,1.36l-1.77,1.39l-4.46,-1.36l-0.41,-8.26l-9.53,-11.53l5.16,-9.84l-4.15,-9.83l-5.22,-0.33l-1.12,-1.93l0.08,-1.67l-0.71,-0.3l-3.99,3.85l-0.9,6.89l-6.22,5.27l-5.55,6.98l-0.54,-0.83l0.3,0.99l-1.01,0.46l-1.14,-0.33l1.6,-0.43l-2.14,-1.47l-1.57,-2.83l-0.93,0.56l-1.28,-0.86l0.19,-0.61l-0.6,0.48l-1.38,-0.35l-1.41,1.95l-2.52,-1.49l-0.81,0.68l-1.17,-0.53l-7.47,1.49l-2.77,-1.52l-0.68,-3.34l0.93,-0.94l1.72,-0.15l0.41,0.81l6.3,2.03l1.23,1.11l1.28,-0.03l-0.51,-0.56l1,-0.86h-2.71l-1.03,-5.97l1.68,-2.91l0.84,0.66l0.87,-0.79l-1.57,-1.19l0.85,-4.51l-3.15,-0.76l-0.85,-2.86l-6.92,-2.31l-3.17,1.19l-1.12,2.53l-2.55,1.34l-2.18,3.85l-1.93,0.46l-1.63,3.85l-4.1,1.87l0.38,4.43l-1.63,0.53l-1.17,-0.43l-1.9,0.99l-1.74,-0.89l-8.42,-1.57l-6.31,2.35l-0.85,-6.58l2.72,-5.95l-1.68,-2.46l-10.1,1.19l-1.58,1.65l-3.42,0.61l-9.27,4.91l-0.73,0.91l2.26,0.28l1,2.68l-0.92,0.56l0.35,1.21l-1.76,1.7l1.35,3.31l6.2,8.17l-0.87,1.04l0.82,1.72l-1.44,-0.28l-4.37,2.2l-2.71,0.25l-5.55,-0.76l-1.22,-4.75l-9.19,0.28l0,0l-0.54,-1.59l-1,-0.13l-0.52,-1.14l0.66,-2.28l-0.81,-4.96l1.9,-2.4l-8.85,-19.84l-0.03,-1.95l2.67,-8.07l0.09,-2.54l-1.41,-2.79l-5.22,-6.4l-0.74,-2.59l1.34,-4.75l1.8,-1.78l4.35,-1.91l1.68,-3.15l0,0l4.29,0.51l9.08,-2.52l3.29,-1.53l2.79,-2.26l1.46,-3.44l2.44,-2.98l0.89,-0.03l0.44,-1.15l3.21,-2.7l11.88,3.97l3.48,0.36l11.87,-3.26l2.5,-2.01l9.97,-2.85l4.79,-3.39l5.51,-5.33l2.25,-5.74l2.45,-2.91l4.57,-1.99l7.91,0.1l3.39,-2.14l2.86,-2.96l3.73,-1.86l4.38,-0.41L259.91,453.21zM249.37,548.14l-1.35,1.97l-0.14,-1.65l-0.51,0.43v-0.94l-0.87,0.53l0.59,0.71l-0.27,1.54l-0.9,0.46l3.59,1.57l0.55,-0.78l-0.38,-0.63l0.98,-0.68l-1.42,-0.38l-0.19,-0.81l0.97,0.1l-0.62,-1.11l1.5,-0.76l0.22,0.53l0.98,-1.16l-0.35,-0.58l-0.74,0.73l-2.93,0.05L249.37,548.14zM251.87,548.95l-0.71,-0.58l-0.41,0.43L251.87,548.95zM242.78,553.78l-0.4,0.53l0.43,-0.1L242.78,553.78zM249.51,557.48l-1.6,0.46l0.59,0.58l-0.66,0.56l1.84,-0.3L249.51,557.48z\"/>\n\t\t<path id=\"NL-NH\" title=\"Noord-Holland\" class=\"land\" d=\"M307.38,228.33l8.62,3.83l7.09,7.5l0,0l-0.36,0.26l0,0l-7.01,-7.42l-8.23,-3.59h-2.78L307.38,228.33zM212.99,155.72l-8.24,1.02l-1.69,0.92l0.81,0.81l-0.55,0.55l-1.39,-0.1l-0.87,-1.34l0.9,-1.68l4.13,-2.44l3.37,0.47l3.42,1.23L212.99,155.72zM268.4,164.24l-2.14,1.73l-0.38,2.49l1.47,-0.26l10.35,20.25l-1.52,18.28l1.22,0.08l0.67,1.17l0.87,3.63l4.43,4.51l1.17,0.39l1.77,0.03l1.41,-0.94l2.52,-3.7l4.46,0.26l3.39,1.77l3.54,-0.7l0.43,0.5l-1.55,0.55l2.15,-0.13l1.85,1.23l0.68,7.14l3.1,1.9l-1.2,-0.1l-0.22,1.38l-2.94,1.67l0.51,1.3l-5.79,1.48l-1.65,7.31l-0.79,1.17l-3.69,2.06l-1.5,2.16l-3.92,1.01l-2.33,2.47l-5.05,0.88l-1.17,-3.33l-2.15,0.16l-2.63,-2.78l-2.2,0.65l-1.17,1.2l-0.21,-0.68l-1.36,0.21l0.11,0.96l-1.08,-1.95l-0.93,0.03l-1.16,1.25l-2.75,1.25l-0.44,1.01l0.81,7.2l-0.43,1.01l1.93,5.38l-0.25,1.66l1.91,1.79l1.91,4.26l-0.11,1.53l2.5,5.16l0.7,0.03l-0.17,1.01l1.08,0.88l0.66,2.2l-2.34,3.53l-1.11,-0.08l-0.47,0.62l0.08,2.72l1.16,1.35l-0.1,1.11l-2.72,-0.13l-1.91,0.96l-0.7,-0.41l1.31,2.07l0.79,-1.06l1.08,0.26l-0.81,1.66l0.02,3.68l4.78,0.91l1.65,-0.41l0.3,-3.26l1.46,-1.92l-1.27,-7.72l1.82,5.26l1.36,1.66l3.75,0.28l-3.75,1.61l-1.09,1.14h-1.17l-0.47,3.21L273.7,296l-0.7,0.34l-0.08,-0.91l-1.15,1.04l-1.5,4.35l-2.07,-0.1l-1.22,1.79l-2.2,1.47l-1.22,3.41l-2.14,1.14l0.43,0.67l-0.63,1.86l-1.82,-1.27l-2.83,1.19l-2.64,-2.38l-0.71,0.83l0.47,1.24l2.55,1.32l-1.22,1.4l-0.47,-1.06l-0.85,0.72l3.29,2.12l2.42,2.92l2.34,0.47l2.18,-1.03l1.12,1.09l-0.98,1.37l0.44,0.96l0.74,-0.08l-0.16,-0.7l1.42,0.54l4.02,-0.21l1.28,-0.93l2.28,0.34l2.99,2.25l4.15,1.06l0,0l2.23,2.07l11,-1.88l9.73,2.45l4.65,3.07l3.81,3.77l0,0l-4.73,3.38l-4.62,-0.83l-1.5,1.37l-0.93,-0.41l-2.53,5.21l-3.47,3.09l-0.49,7.94l-4.37,8.73l-10.82,-0.59l-0.28,-1.13l2.93,-1.96l0.84,-1.49l-1.99,-3.97l-1.9,-1.06l-0.49,0.95l-6.79,0.18l-5.24,-2.14l-0.63,-2.19l3.31,-1.49l-2.18,-2.14l-0.57,-2.86l-1.36,0.57l-1.5,-4.69l4.05,-1.21l0.17,-1.14l1.28,-0.67l1.2,0.93l-0.76,-1.99l-1.68,0.13l-1.23,1.47l-1.5,-1.26l-0.71,0.21l-2.14,2.68h-0.7l-1.36,-3.2l-0.98,-0.41l-2.45,0.18l-2.63,2.63l-4.45,-0.93l-1.2,0.52l0.21,2.24l-2.33,1.7l-1.17,2.24l-6.77,0.13l-5.54,5.26l-2.98,1.37h-2.47l0,0l-1.65,-0.36l-1.55,0.67l-0.92,1.34l-4.76,2.65l-1.23,1.55l-0.92,-0.7l-0.24,-5.59l-3.09,-0.49l-2.66,1.83l-3.61,0.28l-5.13,2.99l-3.24,0.23l-4.6,-1.21l-1.68,0.57l-2.33,-0.54l-1.08,-0.88l-0.16,-1.31l1.39,-2.11l1.25,-6.55l-0.29,-2.04l3.13,-3.17l3.81,-8.44l-4.3,-1.63l-2.14,0.59l-1,1.86l-10.74,-4.85l0,0l10.57,-27.46l0.85,-6.03l0.81,-1.11l-0.6,-1.71l3.07,-7.78l4.05,-22.58l4.65,-39.09l1.42,-6.7l2.28,-5.04l2.67,-9.06l3.67,-14.66l0.82,-3.79l0.7,-14.14l1.68,-4.48l1.42,-1.31l6.98,-0.63l-0.13,0.58l1,-0.05l-0.79,0.66l1.19,1.83l1.16,-0.31l0.55,-1.28l-0.97,-0.86l1.54,-0.31l0.4,2.04l-1.98,5.48l2.6,5.55l10.79,6.54l5.14,-1.36l4.97,-2.51l2.42,-3.06l0.68,-2.33l1.5,-1.34l5.43,-1.57l1.84,0.52l2.47,-0.81l1.33,0.94l19.91,-17.64l0,0l0.65,0.87l0,0L268.4,164.24zM240.79,107.05l1.28,0.26l3.04,5.21l-0.81,1.68l-0.32,-0.87l-0.13,12.46l-0.62,1.89l-3.85,3.39l-0.14,2.71l-1.69,3.05l-1.68,0.87l-1.28,1.89l-3.88,0.94l-4.26,5.54l0.71,0.89l-0.46,0.66l-6.46,-2.05l-0.41,0.34l0.6,1l1.84,0.73l1.44,1.55l-1.52,-0.47l-1.71,1l0.43,0.58l-2.9,0.31l-1.61,1.1l-2.69,-4.01l-0.55,-2.86l0.25,-4.88l1.82,-7.9l4.53,-9.35l11.52,-18.15l4.07,-4.32l1.46,-0.55l1,0.42l2.48,3.92L240.79,107.05z\"/>\n\t\t<path id=\"NL-OV\" title=\"Overijssel\" class=\"land\" d=\"M436.89,186.27L439.24,189.9L450.01,202.23L444.85,207.94L444.66,209.2L437.95,211.62L436.89,213.11L442.69,228.07L445.69,231.64L446.91,231.14L449.32,231.82L450.17,230.65L452.28,230.15L453.87,231.64L456.56,232.36L458.33,234.47L459.46,234.55L460.8,235.8L462.51,234.92L463.94,236.5L465.04,235.59L469.16,234.53L469.7,235.46L469.32,237.15L475.16,241.37L476.94,245.19L477.34,247.9L478.68,249.46L480.5,248.03L482.01,248.08L482.51,247.17L483.94,247.9L485.9,245.43L486.45,247.45L487.93,248.73L490.6,248.36L491.72,245.89L493.23,247.58L497.72,247.9L499.94,249.98L499,240.2L505.03,235.57L515.13,233.3L530.16,239.76L528.64,245.5L530.46,245.61L530.46,245.61L532.66,248.55L531.58,255.51L539.08,262.13L536.01,262.99L532.53,262.21L530.86,265.93L525.35,264.81L529.29,273.04L528.17,281.98L538.12,288.43L540.73,289.21L552.83,289.19L552.82,291.62L554.45,291.28L566.81,295.3L570.11,292.73L572.44,287.81L574.2,286.57L576.38,291.36L577.68,296.77L579.45,298.38L581.68,304.02L585.31,304.43L587.68,311.18L587.57,316.69L584.91,320.41L583.5,323.84L583.23,326.37L580.19,332.75L580.43,336.98L582.96,341.67L586.13,345.21L585.43,346.83L582,349.05L578.88,349.61L576.49,348.66L573.61,349.36L572.73,350.57L572.11,354.15L568.23,360.77L561.36,362.1L560.91,363.8L557.36,367.1L555.86,373.81L553.2,376.38L538.18,376.97L538.18,376.97L531.05,377.02L522.56,374.04L524.24,371.34L523.83,367.33L524.37,365.96L523.64,364.7L520.38,362.98L519.72,362.93L518.94,364.29L517.33,362.95L514.68,365.32L513.35,364.09L513.75,362.62L511.23,360.4L505.43,361.82L499.14,360.79L495.58,361.69L493,359.74L492.06,356.36L489.76,355.64L488.46,353.01L485.27,349.67L483.51,346.24L483.56,344.95L478.04,344.33L473.29,345.26L471.29,346.75L471.2,348.94L468.54,349.36L465.27,349.33L464.06,347.94L461.86,348.94L460.82,348.58L460.74,349.33L457.12,349.07L452.07,348.02L449.76,346.29L445.63,346.26L445.04,348.79L444.32,348.02L444.16,349.18L443.54,348.92L444.41,350.44L444.09,351.21L443.37,349.33L440.95,348.22L440.52,346.57L441.96,345L442.15,343.58L440.19,343.76L437.5,342.71L438.41,340.39L435.57,335.79L435.44,333.29L434.17,330.69L433.84,330.09L431.13,329.73L430.5,325.73L428.93,325.52L429.84,322.4L429.85,317.26L432.72,315.37L430.38,313.82L430.12,312.29L430.75,311.42L433.89,310.82L437.2,309.24L439.17,301.28L438.18,297.73L433.97,292.53L433.51,286L432.16,284.37L428.51,282.27L426.69,278.59L422.05,275.58L420.94,276.9L419.23,277.5L412.34,284.63L410.97,282.66L408.03,284.39L407.41,283.82L406.21,284.37L404.06,279.26L400.85,274.75L398.39,272.57L396.24,273.37L396.24,273.37L394.5,264.76L387.76,257.56L382.57,257.36L382.63,250.5L392.02,250.52L393.27,249.48L402.59,247.43L404.65,248.78L406.99,248.49L410.86,245.87L416.1,244.72L419.66,242.36L420.45,241L418.17,238.48L415.6,237.78L413.08,235.57L407.27,233.98L407.21,233.09L409,231.06L411.88,228.9L412.26,227.31L410.2,223.56L409.09,222.99L405.8,213.68L395.58,205.33L391.99,204.08L389.09,204.08L385.51,199.2L385.51,199.2L389.3,196.56L391.46,198.05L391.81,199.12L392.81,198.55L398.68,200.11L401.99,198.55L406.2,194.58L406.18,192.54L406.89,191.68L409.3,191.16L410.77,191.76L410.79,190.37L413.62,189.43L415.77,194.81L417.86,195.81L421.99,194.68L422.7,196.12L426.43,194.11L426.23,190.92L428.02,190.19L430.79,190.29z\"/>\n\t\t<path id=\"NL-UT\" title=\"Utrecht\" class=\"land\" d=\"M227.2,348.02L229.67,348.02L232.64,346.65L238.18,341.39L244.95,341.26L246.12,339.02L248.45,337.32L248.24,335.07L249.45,334.56L253.89,335.48L256.52,332.85L258.97,332.67L259.95,333.09L261.31,336.28L262.01,336.28L264.15,333.6L264.86,333.4L266.36,334.66L267.6,333.19L269.27,333.06L270.03,335.05L268.83,334.12L267.55,334.79L267.37,335.92L263.32,337.14L264.83,341.83L266.19,341.26L266.76,344.12L268.94,346.26L265.63,347.76L266.27,349.95L271.5,352.09L278.29,351.91L278.78,350.95L280.68,352.01L282.68,355.98L281.84,357.47L278.91,359.43L279.19,360.56L290.02,361.15L294.39,352.42L294.88,344.48L298.34,341.39L300.87,336.18L301.81,336.59L303.31,335.23L307.93,336.05L312.66,332.67L312.66,332.67L323.77,343.25L323.77,343.25L321.95,351.08L328.42,356.36L328.06,359.86L325.54,360.58L326.19,363.57L334.75,365.29L336.21,366.81L335.99,368.54L338.9,369.15L338.71,371.03L340.84,372.09L340.07,373.58L338.34,373.83L337.76,375.2L339.26,376.35L339.89,377.95L336.62,377.61L337.73,378.33L336.27,380.16L336.48,382.83L334.2,384.27L332.49,386.45L336.24,389.05L339.44,388.56L339.85,387.17L342.57,387.35L344.09,385.73L343.04,385.3L343.55,384.4L344.83,384.78L344.94,384.27L343.61,380.83L345.02,380.47L345.43,380.95L346.76,380.23L346.73,382.42L347.33,382.83L347.46,384.68L348.42,385.32L348.68,387.33L347.44,394.87L348.65,395.39L348.44,397.16L351.45,398.29L351.24,402.78L350.37,402.8L352.79,404.62L353.85,407.39L356.11,408.77L357.14,411.93L358.74,413.82L358.16,415.54L358.87,419.43L357.93,419.66L358,420.97L357.09,420.46L355.5,421.87L352.82,420.07L347.49,418.49L345.21,415.77L340.1,413.88L336.9,411.62L333.2,411.7L328.88,410.85L325.76,413.18L320.79,415.08L315.64,414.9L312.82,417.79L310.89,418.77L309.54,418.69L307.09,417.05L302.52,416.18L297.66,412.57L296.46,412.88L293.26,417.41L290.38,417.18L288.21,415.77L288.21,415.77L283.23,413.95L281.68,408.88L278.04,407.08L275.03,406.6L270.33,408.57L265.95,412.72L264.72,415.08L263.12,415.26L260.68,413.34L258.85,413.23L254.45,417.62L249.97,416.59L246.63,421.76L240.43,423.25L239.14,418.2L234.59,413.05L232.55,409.06L231.34,408.65L229.94,406.78L236.28,405.21L235.13,402.39L228.75,403.7L228.31,402.26L228.31,401.34L229.7,400.34L229.43,398.41L232.61,394.39L235.22,392.41L238.53,391.2L239.33,389.56L232.85,390L232.91,388.63L231.74,386.3L231.56,380.06L226.15,376.17L226.41,375.12L228.35,374.25L229.57,370.88L230.84,370.34L233.24,369.57L233.61,371.08L234.49,371.78L238.72,372.01L239.51,371.39L238.92,367.35L242.21,365.73L236.91,362.05L234.52,361.2L229.68,355.41L228.61,355.46L228.38,351.57L227.26,349.72z\"/>\n\t\t<path id=\"NL-ZE\" title=\"Zeeland\" class=\"land\" d=\"M122.86,569.59l0.62,-0.25l0.73,1.57l-1.84,0.35l-2.44,-1.42l1.19,-0.78L122.86,569.59zM29.64,559.63l2.94,0.94l-0.02,0.73l1.47,0.05l1.57,1.85l3.5,0.63l1.41,1.21l11.11,2.28l5.13,5.36l2.75,-0.25l3.88,0.78l-0.57,1.95l1.66,0.88l0.67,-0.25l-0.17,-3.08l3.05,0.08l1.72,1.34l1.52,0.23l-0.49,0.96l1.16,2.25l0.08,-3.06l0.6,1.24l0.82,-1.19l2.85,1.67l4.04,-0.1l1.41,-2.3l2.36,-0.71l3.51,-2.83l6.44,-2.12l0.78,0.61l0.68,-4.05l0.87,-0.94l-0.51,-3.08l1.65,-2.33l6.9,1.37l-0.32,0.53l1.96,1.44l0.57,3.14h0.65l2.14,3.72l6.57,0.63l8.1,7.45l-2.58,-4.12l2.71,-0.71l0.98,1.37l1.6,0.5l1.08,1.74l0.41,-0.63l-1.03,-1.37l2.48,0.13l-2.94,-1.19l4.79,0.1l-0.66,-0.66l-4.43,-0.45l-1.31,-2.25l0.08,-1.16l1.69,-0.1l1.57,-0.96l0.43,3.08l3.4,1.54l-3.07,-2.5l0.33,-1.57l0.6,1.39l0.81,-2.88l2.86,0.43l1.12,0.86l0.65,2.15l2.66,3.49l-10.35,13.89l-16.33,11.71l-0.54,0.96l-2.91,0.68l-0.95,-1.06l-1.84,1.01l-0.57,-0.96l-1.04,0.03l-4.27,2.97l-0.3,-0.38l-1.41,2.34l-2.26,0.4l-0.71,1.94l-3.59,1.03l-1.23,-1.97l-0.48,0.81l-0.92,0.05l-0.29,1.94l-1.06,0.88l-3.77,1.28l-1.23,-1.84l2.29,-2.32l-0.93,-1.56l-4.59,3l-1.11,-0.68l-1.49,0.55l-0.92,-0.38l-2.18,1.11l-2.83,-1.03l-0.09,1.16l-2.01,-1.56l-0.25,-7.59l0.97,-2.67l-2.86,-1.97l-0.71,-0.3l-0.95,0.93l-2.28,-2.6l-4.54,-0.61l-1.01,0.4l-3.5,-1.01l-0.93,-1.31l-1.68,-0.03l-3.34,-2.4l-2.74,0.5l-7.83,-4.37l-1.57,1.87l0.76,1.36l-0.62,1.59l-3.13,-1.99l-1.22,1.21l-1.79,-0.23l-0.81,1.72l-0.87,0.13l-1.19,-1.19l-1.55,0.3l1.92,10.37l-11.93,1.18l-3.85,-0.91l-0.71,-2.7l-1.14,-1.08l-1.14,0.73l-3.24,-4.21l-1.96,-1.06l0.95,-0.78l-0.03,-1.44l-1.68,-1.41l-0.62,-1.57l1.42,-0.76L0,581.39l3.99,-4.52L3.7,574.6l-1.36,-1.77l0.1,-2.65l-1.31,-2.53l2.15,-2.1l0.3,0.38l6.63,-3.39l1.31,-0.2l0.9,1.01l2.86,-1.72l4.64,-1.09l3.5,-2.3l5.13,0.38L29.64,559.63zM52.44,525.81l0.54,-1.01l0.14,0.66L52.44,525.81zM63.8,523.22l1.82,0.41l-3.77,0.23l0.27,-0.96L63.8,523.22zM66.14,522.63l-0.93,-0.69l1.15,0.2L66.14,522.63zM64.77,521.92l0.17,0.84l-2.15,-0.61L64.77,521.92zM51.4,521.69l1.61,1.63l-0.79,2.11l-1.25,-3.12L51.4,521.69zM49.07,519.21l0.41,0.53l-1,-0.18L49.07,519.21zM75.57,469.26l1.31,1.28l4.53,1.43l1.61,-0.31l3.21,-4.08l5.47,-2.24l2.93,0.41l2.25,2.04l6.04,1.35l1.08,3.39l2.61,3.7l0.24,4.94l1.9,4.46l8.1,1.02l2.99,1.07l5.3,0.18l12.41,9.06l2.96,0.97l0,0l-1.68,3.15l-4.35,1.91l-1.8,1.78l-1.34,4.75l0.74,2.59l5.22,6.4l1.41,2.79l-0.09,2.54l-2.67,8.07l0.03,1.95l8.85,19.84l-1.9,2.4l0.81,4.96l-0.66,2.28l0.52,1.14l1,0.13l0.54,1.59l0,0l-5.7,0.25l0.4,-1.92l-1.95,-3.24l-2.88,-1.32l-1.04,0.2l-1.03,-1.62l-7.8,2.81l-11.3,-3.14l-4.32,-3.26l-0.78,-1.52l0.55,-0.86l-2.55,-0.79l-0.16,-1.09L106,551l-3.97,-2l-4.54,-4.53L91,547.86l-1.47,-0.48l0.17,1.29l-0.71,0.15l0.7,2.46l-3.97,9.97l-1.11,0.53l-3.53,-1.06l-2.14,1.54l-6.82,1.64l-6.41,-5.26l0.27,-1.09l-1.95,-0.13l-1.71,-1.11l-1.49,0.83l-2.01,-0.23l-0.92,-0.76l-0.84,-2.94l-0.79,0.08l-2.39,-3.21l-3.54,-2.56l-4.86,-0.2l-1.91,2.15l-4.18,0.41l-1.17,-0.94L38.14,547l-0.55,0.08l-0.1,2.1l-0.79,-0.23v-0.83l-0.33,0.68l-0.33,-0.89l-0.21,0.63l1.63,1.21l-4.3,0.08l-1.79,-2.28l-0.93,0.08l-4,-3.42l-4.53,-7.45l-6.85,-4.92l-3.34,-3.83l0.93,-4.01l9.34,-6.32l11.57,-6.58l4.75,0.05l4.21,1.55l7.77,-2.79l0.97,-5.16l-0.85,-1.04l1.14,-0.2l3.78,-4.22l-0.24,-0.48l0.78,-0.2l0.96,-2.11l-0.51,-0.64l0.78,-0.23l0.03,-2.88l-2.34,-0.74l-1.82,-1.5l-2.22,-4.2l-0.17,-3.29l2.53,-5.2l1.84,-1.4l9.84,-3.19l8.26,0.82l0.68,-3.16L75.57,469.26zM70.57,486l-0.98,-0.99l-0.73,0.13l-0.71,0.51l-0.33,1.76l-4.68,3.18l-5.51,2.16l-0.05,2.82l0.79,0.1l-0.3,1.07l-0.87,0.03l-1.31,3.18l0.9,-0.18l-1.44,1.58l1.46,0.76l-1.58,-0.46l-0.62,2.21l-0.89,-1.53l-1.46,1.27l-1.22,6.05l0.24,0.48l0.84,-0.61l0.03,0.51l1.57,-0.33l2.64,1.91l1.84,0.03l5.6,-1.93l1.9,0.41l1.01,-0.96l3.59,-0.86l2.93,0.56l2.58,-0.61l0.62,0.79l3.05,0.56l1.69,1.8l2.61,4.55l0.52,-0.46l-0.49,0.71l1.65,2.56l-0.19,0.79l-3.75,1.42l-1.77,2.39l-1.27,0.41l-3.47,-3.2l-2.75,0.63l-1.87,1.52l-6.96,-1.83l-4.23,2.46l-4.84,4.9l-0.79,-1.78l0.44,-1.52l1,1.9l0.05,-0.68l-2.96,-5.53l1.9,-2.49l-2.41,2.29l-3.97,-3.2l-1.23,-0.2l-1.25,-3.17l0.29,-1.32l-3.04,1.27l0.28,2.36l2.63,3.76l3.73,2.21l2.36,5.76l2.74,4.24l0.25,-0.46l0.54,0.99l-0.35,-0.91l2.53,-0.41l-1.87,-0.05l-0.46,-0.61l3.58,-1.8l-0.54,-1.04l1.2,-3.04l1.77,-1.14l0.76,0.05l-0.43,1.02l0.68,-0.74l7.03,0.36l2.06,-1.22l3.64,-0.63l1.99,0.81l0.84,1.37l1.88,0.38l0.93,-1.29l0.59,0.84l2.5,-0.69l2.22,0.43l1.91,-1.14l2.25,0.38l2.37,4.64l2.45,-1.07l1.84,0.33l5.41,1.95l0.16,2.05h0.49l-0.17,-2.03l1.96,0.81l0.66,1.34l3.43,2.64l1.17,-0.36l1.3,4.03l-0.6,1.7l3.73,5.6l0.82,0.03l1.42,3.09l7.12,3.65l2.42,-0.41l4.48,-2.61l3.05,0.53l1.55,0.96l1.74,-0.13l-1.03,-13.02l-6.8,-3.07l-0.63,-1.57l0.89,-1.83l-1.52,-1.75l-2.39,-0.41l-1.17,0.74l-1.23,-1.12l-5.16,0.36l-4.41,-0.81l-0.17,-0.96l-1.17,-0.53l-0.57,-2.56l-1.65,-1.04l-0.09,-1.19l-3.69,-1.07l0.43,-1.19l-1.01,-1.5l-3.29,-2.08l-2.17,-0.33l-0.87,-1.17l1.08,-2.87l5.14,-0.99l4.84,-3.13l3.48,-1.07l4.05,0.69l0.98,1.88l1.72,0.97l-0.21,-0.71l4.46,-0.53l2.04,0.51l0.27,-0.74l1.84,-0.28l0.08,-0.56l-0.74,-0.2l1.04,-0.58l-0.06,-0.92l-2.78,0.69l-4.68,-0.13l-1.53,-3.08l-2.48,-1.58l-0.13,-2.64l0.92,-0.1v-0.81l2.37,-2.09l4.49,0.89l6.85,3.89l-0.79,-2.52l-4.83,-2.14l0.16,-1.4l-1.08,0.99l-2.12,-0.74l-0.11,-0.74l3.29,0.08l-0.78,-1.04l-2.61,0.51l2.47,-0.99l0.71,0.36l0.47,-0.64l-1.52,-2.88l-6.52,1.45l-2.17,1.5l1.71,2.06l-0.27,0.76h-0.74l-0.6,2.31l-2.17,1.35l-1.87,2.47l-1.88,0.92l-1.49,-0.66l-6.31,3.36L96.56,506l-3.61,-2.72l-1.36,-2.37l-2.03,1.6l-4.43,-1.09l4.57,-3.99l-1.31,0.33l-4.37,3.46l0.08,-2.42l-1.01,-3.38l-1.39,-1.71l0.08,-1.12l-2.77,-2.62l-1.71,0.2l-3.91,-2.01l0.11,-1.53l-0.82,-1.22l-1.71,-0.53l-1.01,0.13l0.08,0.61L70.57,486z\"/>\n\t\t<path id=\"NL-ZH\" title=\"Zuid-Holland\" class=\"land\" d=\"M155.8,478.39l3.15,2.34l-3.97,-0.15l-1.09,-1.2L155.8,478.39zM143.96,470.44l5.3,1.02l7.77,3.19l0.95,1.27l-3.66,1.43l-4.53,-0.59l-7.82,-5.3l0.79,-1.12L143.96,470.44zM179.49,322.71l10.74,4.85l1,-1.86l2.14,-0.59l4.3,1.63l-3.81,8.44l-3.13,3.17l0.29,2.04l-1.25,6.55l-1.39,2.11l0.16,1.31l1.08,0.88l2.33,0.54l1.68,-0.57l4.6,1.21l3.24,-0.23l5.13,-2.99l3.61,-0.28l2.66,-1.83l3.09,0.49l0.24,5.59l0.92,0.7l1.23,-1.55l4.76,-2.65l0.92,-1.34l1.55,-0.67l1.65,0.36l0,0l0.06,1.7l1.12,1.85l0.22,3.89l1.08,-0.05l4.84,5.79l2.39,0.85l5.3,3.68l-3.29,1.62l0.59,4.04l-0.79,0.62l-4.23,-0.23l-0.89,-0.69l-0.36,-1.52l-2.41,0.77l-1.27,0.54l-1.22,3.37l-1.95,0.87l-0.25,1.05l5.41,3.88l0.17,6.24l1.17,2.34l-0.06,1.36l6.49,-0.44l-0.81,1.64l-3.31,1.21l-2.61,1.98l-3.18,4.03l0.27,1.92l-1.39,1v0.92l0.44,1.44l6.38,-1.31l1.15,2.82l-6.35,1.56l1.41,1.87l1.2,0.41l2.04,4l4.56,5.15l1.28,5.04l6.2,-1.48l3.34,-5.17l4.48,1.03l4.4,-4.38l1.84,0.1l2.44,1.92l1.6,-0.18l1.23,-2.36l4.38,-4.15l4.7,-1.97l3.01,0.49l3.64,1.79l1.55,5.07l4.98,1.82l0,0l-6.93,12.39l0.1,1.61l-0.74,-0.36l-2.2,4.07l-0.65,2.86l-3.67,-0.43l-1.66,3.17l-4.32,1.28l0.14,1.74l1.09,0.67l-2.9,1.87l-1.53,-0.61l-4,0.1l-1.8,-1.58l-0.41,0.43l0.59,1.87l-0.35,1.41l0.95,1.58l3.4,0.46l-1.03,4.98l-2.37,-0.05l0,0l-9.95,-1.81l-4.38,0.41l-3.73,1.86l-2.86,2.96l-3.39,2.14l-7.91,-0.1l-4.57,1.99l-2.45,2.91l-2.25,5.74l-5.51,5.33l-4.79,3.39l-9.97,2.85l-2.5,2.01l-11.87,3.26l-3.48,-0.36l-11.88,-3.97l-3.21,2.7l-0.44,1.15l-0.89,0.03l-2.44,2.98l-1.46,3.44l-2.79,2.26l-3.29,1.53l-9.08,2.52l-4.29,-0.51l0,0l-2.96,-0.97l-12.41,-9.06l-5.3,-0.18l-2.99,-1.07l-8.1,-1.02l-1.9,-4.46l-0.24,-4.94l-2.61,-3.7l-1.08,-3.39l-6.04,-1.35l-2.25,-2.04l-2.93,-0.41l-5.47,2.24l-3.21,4.08l-1.61,0.31l-4.53,-1.43l-1.31,-1.28l0,0l2.9,-1.86l1.44,-3.75l0.32,-1.86l-1.66,-5.51l2.63,-2.99l6.93,-1.58l11.16,-4.44l3.26,0.05l1.58,0.74l0.68,0.64l-1.42,-0.28l-0.22,1.12l3.85,2.78l-0.3,-1.33h1.49l1.49,-2.4l2.42,-1.51l-0.63,-2.58l-5.1,-7.92l0.25,-1.94l3.1,-5.37l-0.11,-0.79l-2.09,-1.43l-0.55,2.07l-2.28,-1.05l-2.91,1.05l-0.85,-0.79l2.44,-9.37l0.05,-2.1l-0.79,0.1v-1.38l0.97,-2.25l1.8,-0.33l1.55,-1.82l1.47,0.67l1.46,-0.23l4,2.28l8.67,-7.33l3.77,-5.75l15.65,-18.89l1.06,0.46l0.74,-1.1l-0.82,0.64l-0.38,-0.75l6.93,-6.94l11.79,-16l10.54,-18.89L179.49,322.71zM108.57,451.27l-0.41,2.76l1,-0.69l17.94,10.29l4.94,1.76l3.45,3.77l3.99,3.11l3.1,4.18l4.24,2.88l6.84,3.21l7.88,0.69l1.58,-0.56l1.79,-4.81l-13.32,-7.26l-10.85,-3.29l-2.91,-3.34l0.06,-2.35l-2.41,-2.68l-2.26,-1.25l-2.82,-0.03l-4.27,-4.8l-4.79,0.51l-1.09,-1.48l-0.32,1.05l-0.6,-0.77l-3.77,-1.33l-2.37,-2.99l-3.05,1.53L108.57,451.27z\"/>\n\t</g>\n</svg>" + }, + "targets": [ + { + "columns": [], + "datasource": { + "uid": "grafanaapi" + }, + "filters": [], + "format": "table", + "global_query_id": "", + "hide": false, + "refId": "GrafanaAlerts", + "root_selector": "", + "source": "url", + "type": "json", + "url": "http://localhost:3000/api/alertmanager/grafana/api/v2/alerts", + "url_options": { + "data": "", + "method": "GET" + } + }, + { + "datasource": { + "uid": "6W2nM-Vnz" + }, + "editorMode": "code", + "expr": "device_scraping", + "hide": false, + "range": true, + "refId": "B" + }, + { + "columns": [], + "datasource": { + "uid": "alertaui" + }, + "filters": [], + "format": "table", + "global_query_id": "", + "hide": false, + "refId": "AlertaAlerts", + "root_selector": "alerts", + "source": "url", + "type": "json", + "url": "http://alerta-web:8080/api/alerts", + "url_options": { + "data": "", + "method": "GET", + "params": [ + { + "key": "status", + "value": "open" + } + ] + } + } + ], + "title": "Panel Title", + "type": "aceiot-svg-panel" + } + ], + "refresh": "5s", + "schemaVersion": 36, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "ace.avg", + "uid": "tMZW8-U7z", + "version": 11, + "weekStart": "" +} diff --git a/docker-compose/grafana/datasources/grafanaapi.yaml b/docker-compose/grafana/datasources/grafanaapi.yaml new file mode 100644 index 0000000000000000000000000000000000000000..a2310cdf2f4432c09581b1f60bbf9ec16a573606 --- /dev/null +++ b/docker-compose/grafana/datasources/grafanaapi.yaml @@ -0,0 +1,36 @@ +apiVersion: 1 + +datasources: + # <string, required> name of the datasource. Required + - name: Grafana API + # <string, required> datasource type. Required + type: yesoreyeram-infinity-datasource + # <string, required> access mode. proxy or direct (Server or Browser in the UI). Required + access: proxy + # <int> org id. will default to orgId 1 if not specified + orgId: 1 + # <string> custom UID which can be used to reference this datasource in other parts of the configuration, if not specified will be generated automatically + uid: grafanaapi + # <string> url + url: http://localhost:3000/api + # <string> Deprecated, use secureJsonData.password + password: + # <string> database user, if used + user: postgres + # <string> database name, if used + database: hdb + # <bool> enable/disable basic auth + basicAuth: false + # <string> basic auth username + basicAuthUser: + # <string> Deprecated, use secureJsonData.basicAuthPassword + basicAuthPassword: + # <bool> enable/disable with credentials headers + withCredentials: + # <bool> mark as default datasource. Max one per org + isDefault: false + # <map> fields that will be converted to json and stored in jsonData + version: 1 + # <bool> allow users to edit datasources from the UI. + editable: false + diff --git a/docker-compose/jupyter/ipython-profiles/stationcontrol-jupyter/startup/01-devices.py b/docker-compose/jupyter/ipython-profiles/stationcontrol-jupyter/startup/01-devices.py index 1434393a85f0ed506d30303964f43c6c5ee0fa33..d9cdd0e945bc0f0c54eacedc767c364e7336f89e 100644 --- a/docker-compose/jupyter/ipython-profiles/stationcontrol-jupyter/startup/01-devices.py +++ b/docker-compose/jupyter/ipython-profiles/stationcontrol-jupyter/startup/01-devices.py @@ -11,7 +11,8 @@ tilebeam = DeviceProxy("STAT/TileBeam/1") pdu = DeviceProxy("STAT/PDU/1") beamlet = DeviceProxy("STAT/Beamlet/1") digitalbeam = DeviceProxy("STAT/DigitalBeam/1") +antennafield = DeviceProxy("STAT/AntennaField/1") docker = DeviceProxy("STAT/Docker/1") # Put them in a list in case one wants to iterate -devices = [apsct, apspu, recv, sdp, sst, xst, unb2, boot, tilebeam, beamlet, digitalbeam, docker] +devices = [apsct, apspu, recv, sdp, sst, xst, unb2, boot, tilebeam, beamlet, digitalbeam, antennafield, docker] diff --git a/docker-compose/prometheus-node-exporter.yml b/docker-compose/prometheus-node-exporter.yml new file mode 100644 index 0000000000000000000000000000000000000000..da82726fc2ebec19bdba44067ea813adeb618170 --- /dev/null +++ b/docker-compose/prometheus-node-exporter.yml @@ -0,0 +1,25 @@ +# +# Docker compose file that launches Prometheus Node Exporter +# +# Provides system metrics for Prometheus to scrape +# + +version: '2' + +services: + prometheus-node-exporter: + image: prom/node-exporter + container_name: ${CONTAINER_NAME_PREFIX}prometheus-node-exporter + logging: + driver: "json-file" + options: + max-size: "100m" + max-file: "10" + network_mode: host # run on the host to be able to access host network statistics + logging: + driver: syslog + options: + syslog-address: udp://${LOG_HOSTNAME}:1514 + syslog-format: rfc3164 + tag: "{{.Name}}" + restart: unless-stopped diff --git a/docker-compose/prometheus.yml b/docker-compose/prometheus.yml index 8029e9ba6d83e4af824ca307b8aae17af919333e..f91a1a17428b5abf7e53149377a21629539b3bb9 100644 --- a/docker-compose/prometheus.yml +++ b/docker-compose/prometheus.yml @@ -23,6 +23,8 @@ services: max-file: "10" networks: - control + extra_hosts: + - "host.docker.internal:host-gateway" volumes: - prometheus-data:/prometheus ports: diff --git a/docker-compose/prometheus/prometheus.yml b/docker-compose/prometheus/prometheus.yml index ac9c549be45d6aab48f585dd6ab234cfc1f15449..32746772773146e4b356c8c019e41c5356fecfd1 100644 --- a/docker-compose/prometheus/prometheus.yml +++ b/docker-compose/prometheus/prometheus.yml @@ -9,3 +9,8 @@ scrape_configs: - targets: - "tango-prometheus-exporter:8000" + - job_name: host + scrape_interval: 60s + static_configs: + - targets: + - "host.docker.internal:9100" diff --git a/docker-compose/tango-prometheus-exporter/code/pip-requirements.txt b/docker-compose/tango-prometheus-exporter/code/pip-requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..771b4421928ac8ddc6ac2c328b4827b238b94c06 --- /dev/null +++ b/docker-compose/tango-prometheus-exporter/code/pip-requirements.txt @@ -0,0 +1,2 @@ +prometheus_client +python-logstash-async diff --git a/sbin/run_integration_test.sh b/sbin/run_integration_test.sh index b703eae61fb5572463f58c3079d133ed30ad37ee..fd18a0ee309d322c6de8e6bf12c8df9c183b235c 100755 --- a/sbin/run_integration_test.sh +++ b/sbin/run_integration_test.sh @@ -20,7 +20,7 @@ sleep 1 # dsconfig container must be up and running... # shellcheck disable=SC2016 echo '/usr/local/bin/wait-for-it.sh ${TANGO_HOST} --strict --timeout=300 -- true' | make run dsconfig bash - -DEVICES="device-boot device-apsct device-apspu device-sdp device-pdu device-recv device-sst device-unb2 device-xst device-beamlet device-digitalbeam device-tilebeam device-pdu" +DEVICES="device-boot device-apsct device-apspu device-sdp device-recv device-sst device-unb2 device-xst device-beamlet device-digitalbeam device-tilebeam device-pdu device-antennafield" SIMULATORS="sdptr-sim recv-sim unb2-sim apsct-sim apspu-sim" # Build only the required images, please do not build everything that makes CI diff --git a/tangostationcontrol/docs/source/devices/antennafield.rst b/tangostationcontrol/docs/source/devices/antennafield.rst new file mode 100644 index 0000000000000000000000000000000000000000..e91a3ba71ec4b417fdadc7a9183baef16e75bf31 --- /dev/null +++ b/tangostationcontrol/docs/source/devices/antennafield.rst @@ -0,0 +1,4 @@ +antennfield +==================== + +``antennafield == DeviceProxy("STAT/AntennaField/1")`` diff --git a/tangostationcontrol/docs/source/index.rst b/tangostationcontrol/docs/source/index.rst index d89a0cda0bef89798497ae94263cd0204c4dfe3a..263bcd064268839baa452e087f1c732a8ea92ffa 100644 --- a/tangostationcontrol/docs/source/index.rst +++ b/tangostationcontrol/docs/source/index.rst @@ -22,6 +22,7 @@ Even without having access to any LOFAR2.0 hardware, you can install the full st devices/tilebeam devices/beamlet devices/digitalbeam + devices/antennafield devices/boot devices/docker devices/pdu diff --git a/tangostationcontrol/requirements.txt b/tangostationcontrol/requirements.txt index 62cf05034ea7b40c479f6b75eb4c8d2641f2dcf9..b65be92168032276a4120804d581d4bc95e6c028 100644 --- a/tangostationcontrol/requirements.txt +++ b/tangostationcontrol/requirements.txt @@ -13,3 +13,4 @@ docker >= 5.0.3 # Apache 2 python-logstash-async >= 2.3.0 # MIT python-casacore >= 3.3.1 # LGPLv3 etrs-itrs@git+https://github.com/brentjens/etrs-itrs # license pending +lofarantpos >= 0.5.0 # Apache 2 diff --git a/tangostationcontrol/setup.cfg b/tangostationcontrol/setup.cfg index 997b7947d27f34b8c30db7eb320bb5384b546d38..de930c9d399dd5412884f37b3957a698d330b163 100644 --- a/tangostationcontrol/setup.cfg +++ b/tangostationcontrol/setup.cfg @@ -39,6 +39,7 @@ console_scripts = l2ss-tilebeam = tangostationcontrol.devices.tilebeam:main l2ss-beamlet = tangostationcontrol.devices.sdp.beamlet:main l2ss-digitalbeam = tangostationcontrol.devices.sdp.digitalbeam:main + l2ss-antennafield = tangostationcontrol.devices.antennafield:main l2ss-boot = tangostationcontrol.devices.boot:main l2ss-docker-device = tangostationcontrol.devices.docker_device:main l2ss-observation = tangostationcontrol.devices.observation:main diff --git a/tangostationcontrol/tangostationcontrol/beam/geo.py b/tangostationcontrol/tangostationcontrol/beam/geo.py index 033a6c4e4293573d05cb3fa09bcab5071c88a97b..2c8195796811237750becb29f28fcf904f34f03d 100644 --- a/tangostationcontrol/tangostationcontrol/beam/geo.py +++ b/tangostationcontrol/tangostationcontrol/beam/geo.py @@ -1,5 +1,7 @@ import etrsitrs +import lofarantpos.geo import numpy +import math """ LOFAR station positions are measured in ETRS89, which are the coordinates of the position as it would be in 1989. @@ -16,18 +18,34 @@ import numpy The ETRSitrs package does all the transformation calculations for us. """ +def _apply_fn_on_one_element_or_array(fn, array: numpy.array) -> numpy.array: + if array.ndim == 1: + # convert a single coordinate triple + return fn(array) + else: + # convert each coordinate triple + return numpy.apply_along_axis(fn, 1, array) + def ETRS_to_ITRF(ETRS_coordinates: numpy.array, ITRF_reference_frame: str = "ITRF2005", ITRF_reference_epoch: float = 2015.5) -> numpy.array: """ Convert an array of coordinate triples from ETRS to ITRF, in the given reference frame and epoch. """ # fetch converter ETRS_to_ITRF_fn = etrsitrs.convert_fn("ETRF2000", ITRF_reference_frame, ITRF_reference_epoch) - if ETRS_coordinates.ndim == 1: - # convert a single coordinate triple - ITRF_coordinates = ETRS_to_ITRF_fn(ETRS_coordinates) - else: - # convert each coordinate triple - ITRF_coordinates = numpy.apply_along_axis(ETRS_to_ITRF_fn, 1, ETRS_coordinates) + return _apply_fn_on_one_element_or_array(ETRS_to_ITRF_fn, ETRS_coordinates) + +def ETRS_to_GEO(ETRS_coordinates: numpy.array) -> numpy.array: + """ Convert an array of coordinate triples from ETRS to latitude/longitude (degrees). """ + + def ETRS_to_GEO_fn(etrs_coords): + geo_coords = lofarantpos.geo.geographic_from_xyz(etrs_coords) + + return numpy.array([ + geo_coords['lat_rad'] * 180 / math.pi, + geo_coords['lon_rad'] * 180 / math.pi + ]) + + return _apply_fn_on_one_element_or_array(ETRS_to_GEO_fn, ETRS_coordinates) - # return computed ITRF coordinates - return ITRF_coordinates +# Geo coordinates are only used for rough positioning. The difference between ITRF and ETRS matters little here +ITRF_to_GEO = ETRS_to_GEO diff --git a/tangostationcontrol/tangostationcontrol/devices/README.md b/tangostationcontrol/tangostationcontrol/devices/README.md index 19d72e35c4f09d06d500d997d5255c1abdd53b77..378cece58eaa3ebe181e129e6f2f6cb6c2b8b1c3 100644 --- a/tangostationcontrol/tangostationcontrol/devices/README.md +++ b/tangostationcontrol/tangostationcontrol/devices/README.md @@ -10,7 +10,7 @@ If a new device is added, it will (likely) need to be referenced in several plac - Adjust `docker-compose/jupyter/ipython-profiles/stationcontrol-jupyter/startup/01-devices.py` to make an alias for it available in Jupyter, - Adjust `tangostationcontrol/tangostationcontrol/devices/boot.py` to add the device to the station initialisation sequence, - Add to `docker-compose/` to create a YaML file to start the device in a docker container. NOTE: it needs a unique 57xx port assigned, - current _unused_ port value: 5715 + current _unused_ port value: 5716 - Adjust `tangostationcontrol/setup.cfg` to add an entry point for the device in the package installation, - Add to `tangostationcontrol/tangostationcontrol/integration_test/default/devices/` to add an integration test, - Adjust `sbin/run_integration_test.sh` to have the device started when running the integration tests, diff --git a/tangostationcontrol/tangostationcontrol/devices/antennafield.py b/tangostationcontrol/tangostationcontrol/devices/antennafield.py new file mode 100644 index 0000000000000000000000000000000000000000..e694649c3de773d4e380cd3feabbe02eda2e3a12 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/devices/antennafield.py @@ -0,0 +1,195 @@ +# -*- coding: utf-8 -*- +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +""" AntennaField Device Server for LOFAR2.0 + +""" +from tango import DeviceProxy, DevSource +from tango.server import device_property, attribute, AttrWriteType +import numpy + +from tangostationcontrol.common.entrypoint import entry +from tangostationcontrol.devices.lofar_device import lofar_device +from tangostationcontrol.common.lofar_logging import device_logging_to_python, log_exceptions +from tangostationcontrol.devices.device_decorators import fault_on_error + +import logging +logger = logging.getLogger() + + +__all__ = ["AntennaField", "main"] + +NUMBER_OF_HBAT = 48 +NUMBER_OF_ELEMENTS_PER_TILE = 16 + +class mapped_attribute(attribute): + def __init__(self, mapping_attribute, dtype, max_dim_x, max_dim_y=0, access=AttrWriteType.READ, **kwargs): + + if access == AttrWriteType.READ_WRITE: + @fault_on_error() + def write_func_wrapper(device, value): + write_func = device.set_mapped_attribute(mapping_attribute, value) + + self.fset = write_func_wrapper + + @fault_on_error() + def read_func_wrapper(device): + return device.get_mapped_attribute(mapping_attribute) + + self.fget = read_func_wrapper + + super().__init__(dtype=dtype, max_dim_y=max_dim_y, max_dim_x=max_dim_x, access=access, fisallowed="is_attribute_wrapper_allowed", **kwargs) + + +@device_logging_to_python() +class AntennaField(lofar_device): + + HBAT_Power_to_RECV_mapping = device_property( + dtype=(numpy.int32,), + doc='The mapping of HBAT power lines to RECV mapping. Each RECV can handle 96 inputs. The HBAT number is the index and the value shows to which receiver device it is connected and on which input. The first integer is the input. The second integer is the RECV id. Example: [0, 3] = first receiver of property RECV_devices with input 3. -1 means that the HBAT is not connected. The property is stored in a one dimensional structure. It needs to be reshaped to a list of lists of two items.', + mandatory=False, + default_value = [-1] * NUMBER_OF_HBAT * 2 + ) + + HBAT_Control_to_RECV_mapping = device_property( + dtype=(numpy.int32,), + doc='The mapping of HBAT control lines to RECV mapping. Each RECV can handle 96 inputs. The HBAT number is the index and the value shows to which receiver device it is connected and on which input. The first integer is the input. The second interger is the RECV id. Example: [1, 3] = STAT/RECV/1 with input 3. -1 means that the HBAT is not connected. The property is stored in a one dimensional structure. It needs to be reshaped to a list of lists of two items.', + mandatory=False, + default_value = [-1] * NUMBER_OF_HBAT * 2 + ) + + RECV_devices = device_property( + dtype=(str,), + doc='Which Recv devices are in use by the AntennaField. The order is important and it should match up with the order of the mapping.', + mandatory=False, + default_value = [] + ) + + HBAT_ANT_mask_RW = mapped_attribute("ANT_mask_RW", dtype=(numpy.bool_,), max_dim_x=NUMBER_OF_HBAT, access=AttrWriteType.READ_WRITE) + HBAT_BF_delay_steps_R = mapped_attribute("HBAT_BF_delay_steps_R", dtype=((numpy.int64,),), max_dim_x=NUMBER_OF_ELEMENTS_PER_TILE * 2, max_dim_y=NUMBER_OF_HBAT) + HBAT_BF_delay_steps_RW = mapped_attribute("HBAT_BF_delay_steps_RW", dtype=((numpy.int64,),), max_dim_x=NUMBER_OF_ELEMENTS_PER_TILE * 2, max_dim_y=NUMBER_OF_HBAT, access=AttrWriteType.READ_WRITE) + HBAT_LED_on_R = mapped_attribute("HBAT_LED_on_R", dtype=((numpy.bool_,),), max_dim_x=NUMBER_OF_ELEMENTS_PER_TILE * 2, max_dim_y=NUMBER_OF_HBAT) + HBAT_LED_on_RW = mapped_attribute("HBAT_LED_on_RW", dtype=((numpy.bool_,),), max_dim_x=NUMBER_OF_ELEMENTS_PER_TILE * 2, max_dim_y=NUMBER_OF_HBAT, access=AttrWriteType.READ_WRITE) + HBAT_PWR_LNA_on_R = mapped_attribute("HBAT_PWR_LNA_on_R", dtype=((numpy.bool_,),), max_dim_x=NUMBER_OF_ELEMENTS_PER_TILE * 2, max_dim_y=NUMBER_OF_HBAT) + HBAT_PWR_LNA_on_RW = mapped_attribute("HBAT_PWR_LNA_on_RW", dtype=((numpy.bool_,),), max_dim_x=NUMBER_OF_ELEMENTS_PER_TILE * 2, max_dim_y=NUMBER_OF_HBAT, access=AttrWriteType.READ_WRITE) + HBAT_PWR_on_R = mapped_attribute("HBAT_PWR_on_R", dtype=((numpy.bool_,),), max_dim_x=NUMBER_OF_ELEMENTS_PER_TILE * 2, max_dim_y=NUMBER_OF_HBAT) + HBAT_PWR_on_RW = mapped_attribute("HBAT_PWR_on_RW", dtype=((numpy.bool_,),), max_dim_x=NUMBER_OF_ELEMENTS_PER_TILE * 2, max_dim_y=NUMBER_OF_HBAT, access=AttrWriteType.READ_WRITE) + + @log_exceptions() + def configure_for_initialise(self): + super().configure_for_initialise() + self.__setup_all_receiver_proxies() + self.__setup_mapper() + + def __setup_all_receiver_proxies(self): + self.recv_proxies = [] + + for recv in self.RECV_devices: + recv_proxy = DeviceProxy(recv) + # Want to force non-cached results from the receiver proxies + recv_proxy.set_source(DevSource.DEV) + self.recv_proxies.append(recv_proxy) + + def __setup_mapper(self): + number_of_receivers = len(self.RECV_devices) + # Reshape of mapping is needed because properties are stored in 1d arrays + control_mapping = numpy.reshape(self.HBAT_Control_to_RECV_mapping, (NUMBER_OF_HBAT, 2)) + power_mapping = numpy.reshape(self.HBAT_Power_to_RECV_mapping, (NUMBER_OF_HBAT, 2)) + self.__mapper = HBATToRecvMapper(control_mapping, power_mapping, number_of_receivers) + + def get_mapped_attribute(self, mapped_point): + recv_results = [] + + for recv_proxy in self.recv_proxies: + result = recv_proxy.read_attribute(mapped_point) + recv_results.append(result) + + mapped_values = self.__mapper.map_read(mapped_point, recv_results) + + return mapped_values + + def set_mapped_attribute(self, mapped_point, value): + mapped_value = self.__mapper.map_write(mapped_point, value) + + for idx, recv_proxy in enumerate(self.recv_proxies): + recv_proxy.write_attribute(mapped_point, mapped_value[idx]) + + +class HBATToRecvMapper(object): + def __init__(self, hbat_control_to_recv_mapping, hbat_power_to_recv_mapping, number_of_receivers): + self.__control_mapping = hbat_control_to_recv_mapping + self.__power_mapping = hbat_power_to_recv_mapping + self.__number_of_receivers = number_of_receivers + self.__default_value_mapping_read = { + "ANT_mask_RW": [False] * 48, + "HBAT_BF_delay_steps_R": [[0] * 32] * 48, + "HBAT_BF_delay_steps_RW": [[0] * 32] * 48, + "HBAT_LED_on_R": [[False] * 32] * 48, + "HBAT_LED_on_RW": [[False] * 32] * 48, + "HBAT_PWR_LNA_on_R": [[False] * 32] * 48, + "HBAT_PWR_LNA_on_RW": [[False] * 32] * 48, + "HBAT_PWR_on_R": [[False] * 32] * 48, + "HBAT_PWR_on_RW": [[False] * 32] * 48 + } + self.__default_value_mapping_write = { + "ANT_mask_RW": [False] * 96, + "HBAT_BF_delay_steps_RW": [[0] * 32] * 96, + "HBAT_LED_on_RW": [[False] * 32] * 96, + "HBAT_PWR_LNA_on_RW": [[False] * 32] * 96, + "HBAT_PWR_on_RW": [[False] * 32] * 96 + } + self.__reshape_attributes = { + "ANT_mask_RW": [32, 3] + } + + def map_read(self, mapped_attribute, recv_results): + default_values = self.__default_value_mapping_read[mapped_attribute] + + return self._mapped_r_values(recv_results, default_values) + + def map_write(self, mapped_attribute, set_values): + default_values = self.__default_value_mapping_write[mapped_attribute] + + mapped_values = self._mapped_rw_values(set_values, default_values) + + if mapped_attribute in self.__reshape_attributes: + mapped_values = numpy.reshape(mapped_values, + (self.__number_of_receivers, + self.__reshape_attributes[mapped_attribute][0], + self.__reshape_attributes[mapped_attribute][1])) + + return mapped_values + + def _mapped_r_values(self, recv_results, default_values): + mapped_values = default_values + + for idx, mapping in enumerate(self.__control_mapping): + recv = mapping[0] + rcu = mapping[1] + if recv > 0: + mapped_values[idx] = recv_results[recv - 1][rcu] + + return mapped_values + + def _mapped_rw_values(self, set_values, default_values): + mapped_values = [] + + for _ in range(self.__number_of_receivers): + mapped_values.append(default_values) + + for idx, mapping in enumerate(self.__control_mapping): + recv = mapping[0] + rcu = mapping[1] + if recv > 0: + mapped_values[recv - 1][rcu] = set_values[idx] + + return mapped_values + +# ---------- +# Run server +# ---------- +def main(**kwargs): + """Main function of the ObservationControl module.""" + return entry(AntennaField, **kwargs) diff --git a/tangostationcontrol/tangostationcontrol/devices/boot.py b/tangostationcontrol/tangostationcontrol/devices/boot.py index 450df1b6fc233219168d3320a8acd8591361cb13..452dc1916fe1a13fb340bd4f3b2e568369448927 100644 --- a/tangostationcontrol/tangostationcontrol/devices/boot.py +++ b/tangostationcontrol/tangostationcontrol/devices/boot.py @@ -244,6 +244,7 @@ class Boot(lofar_device): "STAT/Beamlet/1", "STAT/TileBeam/1", # Accesses RECV and Beamlet "STAT/DigitalBeam/1", + "STAT/AntennaField/1", ], ) diff --git a/tangostationcontrol/tangostationcontrol/devices/lofar_device.py b/tangostationcontrol/tangostationcontrol/devices/lofar_device.py index cc276428d962fa006d2ef0619790b65c1f2ba270..6f0f1de56b5bd0ade282aeede42fd719ce375b13 100644 --- a/tangostationcontrol/tangostationcontrol/devices/lofar_device.py +++ b/tangostationcontrol/tangostationcontrol/devices/lofar_device.py @@ -143,11 +143,12 @@ class lofar_device(Device, metaclass=DeviceMeta): @log_exceptions() def Initialise(self): """ - Command to ask for initialisation of this device. Can only be called in FAULT or OFF state. + Command to ask for initialisation of this device. Can only be called in OFF state. :return:None """ + self.set_state(DevState.INIT) self.set_status("Device is in the INIT state.") diff --git a/tangostationcontrol/tangostationcontrol/devices/recv.py b/tangostationcontrol/tangostationcontrol/devices/recv.py index 93233f86f4a0dd0291205dea5f30e1ab9ae8c5fc..1254fbdc35e4975751c34836da248778a3468cd0 100644 --- a/tangostationcontrol/tangostationcontrol/devices/recv.py +++ b/tangostationcontrol/tangostationcontrol/devices/recv.py @@ -22,7 +22,7 @@ from math import pi # Additional import from tangostationcontrol.beam.hba_tile import HBATAntennaOffsets -from tangostationcontrol.beam.geo import ETRS_to_ITRF +from tangostationcontrol.beam.geo import ETRS_to_ITRF, ITRF_to_GEO from tangostationcontrol.common.entrypoint import entry from tangostationcontrol.common.lofar_logging import device_logging_to_python from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper @@ -234,6 +234,10 @@ class RECV(opcua_device): doc='Absolute reference position of antenna field, in ITRF', dtype=(numpy.float,), max_dim_x=3) + Antenna_Field_Reference_GEO_R = attribute(access=AttrWriteType.READ, + doc='Absolute reference position of antenna field, in latitude/longitude (degrees)', + dtype=(numpy.float,), max_dim_x=2) + HBAT_antenna_ITRF_offsets_R = attribute(access=AttrWriteType.READ, doc='Offsets of the antennas within a tile, in ITRF ("iHBADeltas"). True shape: 96x16x3.', dtype=((numpy.float,),), max_dim_x=48, max_dim_y=96) @@ -242,6 +246,10 @@ class RECV(opcua_device): doc='Absolute reference position of each tile, in ITRF', dtype=((numpy.float,),), max_dim_x=3, max_dim_y=96) + HBAT_reference_GEO_R = attribute(access=AttrWriteType.READ, + doc='Absolute reference position of each tile, in latitude/longitude (degrees)', + dtype=((numpy.float,),), max_dim_x=2, max_dim_y=96) + def read_Antenna_Field_Reference_ITRF_R(self): # provide ITRF field coordinates if they were configured if self.Antenna_Field_Reference_ITRF: @@ -250,6 +258,9 @@ class RECV(opcua_device): # calculate them from ETRS coordinates if not, using the configured ITRF reference ETRS_coordinates = numpy.array(self.Antenna_Field_Reference_ETRS).reshape(3) return ETRS_to_ITRF(ETRS_coordinates, self.ITRF_Reference_Frame, self.ITRF_Reference_Epoch) + + def read_Antenna_Field_Reference_GEO_R(self): + return ITRF_to_GEO(self.read_Antenna_Field_Reference_ITRF_R()) def read_HBAT_antenna_ITRF_offsets_R(self): base_antenna_offsets = numpy.array(self.HBAT_base_antenna_offsets).reshape(16,3) @@ -274,6 +285,9 @@ class RECV(opcua_device): ETRS_coordinates = numpy.array(self.HBAT_reference_ETRS).reshape(96,3) return ETRS_to_ITRF(ETRS_coordinates, self.ITRF_Reference_Frame, self.ITRF_Reference_Epoch) + def read_HBAT_reference_GEO_R(self): + return ITRF_to_GEO(self.read_HBAT_reference_ITRF_R()) + # ---------- # Summarising Attributes # ---------- diff --git a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_antennafield.py b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_antennafield.py new file mode 100644 index 0000000000000000000000000000000000000000..2b34767d6c74df2199017e1a884a0e2165af7fb0 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_antennafield.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the LOFAR 2.0 Station Software +# +# +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +from tangostationcontrol.integration_test.device_proxy import TestDeviceProxy + +from .base import AbstractTestBases + +class TestAntennaFieldDevice(AbstractTestBases.TestDeviceBase): + + def setUp(self): + super().setUp("STAT/AntennaField/1") + self.proxy.put_property({"RECV_devices": ["STAT/RECV/1"], + "HBAT_Power_to_RECV_mapping": [1, 1, 1, 0] + [-1] * 92}) + self.recv_proxy = self.setup_recv_proxy() + + def setup_recv_proxy(self): + # setup RECV + recv_proxy = TestDeviceProxy("STAT/RECV/1") + recv_proxy.off() + recv_proxy.warm_boot() + recv_proxy.set_defaults() + return recv_proxy + + def test_property_recv_devices_has_one_receiver(self): + result = self.proxy.get_property("RECV_devices") + self.assertSequenceEqual(result["RECV_devices"], ["STAT/RECV/1"]) diff --git a/tangostationcontrol/tangostationcontrol/integration_test/default/toolkit/test_archiver.py b/tangostationcontrol/tangostationcontrol/integration_test/default/toolkit/test_archiver.py index 091db2c253e5e163167afc204170d89cf087c61a..05cfe5bf41241d2cf142479026d91b07f332e384 100644 --- a/tangostationcontrol/tangostationcontrol/integration_test/default/toolkit/test_archiver.py +++ b/tangostationcontrol/tangostationcontrol/integration_test/default/toolkit/test_archiver.py @@ -136,6 +136,55 @@ class TestArchiver(BaseIntegrationTestCase): """ sdp_proxy.off() + def test_archive_image_boolean_attribute(self): + """Test if a boolean image attribute is correctly archived""" + # Start RECV Device + recv_proxy = TestDeviceProxy("STAT/RECV/1") + recv_proxy.off() + time.sleep(1) # To be deleted with L2SS-592 + recv_proxy.initialise() + time.sleep(1) # To be deleted with L2SS-592 + self.assertEqual(DevState.STANDBY, recv_proxy.state()) + recv_proxy.set_defaults() + recv_proxy.on() + self.assertEqual(DevState.ON, recv_proxy.state()) + + """ + # Safety operation that prevents event subscriber to go in Fault state + self.archiver.remove_attributes_in_error() + time.sleep(3) + """ + polling_period=1000 + archive_event_period=5000 + attr_fullname = 'stat/recv/1/ant_mask_rw' # boolean 3x32 + self.archiver.add_attribute_to_archiver(attr_fullname, polling_period, archive_event_period) + time.sleep(3) + # Test if the attribute has been correctly added to event subscriber + self.assertTrue(self.archiver.is_attribute_archived(attribute_fqdn(attr_fullname))) + + # Retrieve data from DB views + self.retriever = RetrieverTimescale() + self.assertIsNotNone(self.retriever) + records = self._wait_for_archiving(attr_fullname, archive_event_period) + self.assertTrue(len(records)>0) + item = records[-1] # last table record + self.assertEqual('stat/recv/1',item.device) # column device + self.assertEqual('ant_mask_rw',item.name) # column attribute + self.assertEqual(datetime,type(item.data_time)) # column datetime + self.assertEqual(int,type(item.x)) # column index x + self.assertEqual(int,type(item.y)) # column index y + self.assertEqual(int,type(item.value)) # column value (bool stored as int) + self.assertLessEqual(item.value,1) # column value (must be 0 or 1) + + """ + # Remove attribute at the end of the test + self.archiver.remove_attribute_from_archiver(attr_fullname) + time.sleep(3) + # Test if the attribute has been correctly removed + self.assertFalse(self.archiver.is_attribute_archived(attribute_fqdn(attr_fullname))) + """ + recv_proxy.off() + def test_get_maximum_device_load(self): """ Test if the maximum device load is correctly computed """ # Start RECV Device diff --git a/tangostationcontrol/tangostationcontrol/test/beam/test_geo.py b/tangostationcontrol/tangostationcontrol/test/beam/test_geo.py index 858b3f32e954d19271f8a0dc6fc3cba7b92f47e2..5694376be684b96e74a6197e5e8c3d13f3df6d39 100644 --- a/tangostationcontrol/tangostationcontrol/test/beam/test_geo.py +++ b/tangostationcontrol/tangostationcontrol/test/beam/test_geo.py @@ -7,7 +7,7 @@ # Distributed under the terms of the APACHE license. # See LICENSE.txt for more info. -from tangostationcontrol.beam.geo import ETRS_to_ITRF +from tangostationcontrol.beam.geo import ETRS_to_ITRF, ETRS_to_GEO from tangostationcontrol.test import base @@ -41,3 +41,32 @@ class TestETRS_to_ITRF(base.TestCase): LOFAR1_CS001_LBA_ITRF = [3826923.50275, 460915.488115, 5064643.517] numpy.testing.assert_almost_equal(CS001_LBA_ITRF, LOFAR1_CS001_LBA_ITRF, decimal=1.5) + +class TestETRS_to_GEO(base.TestCase): + def test_convert_single_coordinate(self): + """ Convert a single coordinate. """ + ETRS_coords = numpy.array([1.0, 1.0, 1.0]) + GEO_coords = ETRS_to_GEO(ETRS_coords) + + self.assertEqual((2,), GEO_coords.shape) + + def test_convert_array(self): + """ Convert an array of coordinates. """ + ETRS_coords = numpy.array([ [1.0, 1.0, 1.0], [2.0, 2.0, 2.0], [3.0, 3.0, 3.0] ]) + GEO_coords = ETRS_to_GEO(ETRS_coords) + + self.assertEqual((3,2), GEO_coords.shape) + + def test_verify_CS001_LBA(self): + """ Verify if the calculated CS001LBA phase center matches those calculated in LOFAR1. """ + + # See CLBA in MAC/Deployment/data/Coordinates/ETRF_FILES/CS001/CS001-antenna-positions-ETRS.csv + CS001_LBA_ETRS = [3826923.942, 460915.117, 5064643.229] + + # Convert to GEO + CS001_LBA_GEO = ETRS_to_GEO(numpy.array(CS001_LBA_ETRS)) + + # verify against actual position + LOFAR1_CS001_LBA_GEO = [52.911, 6.868] + + numpy.testing.assert_almost_equal(CS001_LBA_GEO, LOFAR1_CS001_LBA_GEO, decimal=3) diff --git a/tangostationcontrol/tangostationcontrol/test/devices/test_antennafield_device.py b/tangostationcontrol/tangostationcontrol/test/devices/test_antennafield_device.py new file mode 100644 index 0000000000000000000000000000000000000000..189cd08f6536fdeaa8f895b280d889e137ad63fa --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/test/devices/test_antennafield_device.py @@ -0,0 +1,298 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the LOFAR 2.0 Station Software +# +# +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +import numpy +import unittest + +from tangostationcontrol.devices.antennafield import HBATToRecvMapper + +class TestHBATToRecvMapper(unittest.TestCase): + # A mapping where HBATs are all not mapped to power RCUs + power_not_connected = [[-1, -1] * 48] + # A mapping where HBATs are all not mapped to control RCUs + control_not_connected = [[-1, -1] * 48] + # A mapping where first two HBATs are mapped on the first Receiver. + # The first HBAT control line on RCU 1 and the second HBAT control line on RCU 0. + control_hba_0_and_1_on_rcu_1_and_0_of_recv_1 = [[1, 1], [1, 0]] + [[-1, -1]] * 46 + + def test_ant_read_mask_r_no_mapping(self): + mapper = HBATToRecvMapper(self.control_not_connected, self.power_not_connected, 3) + + receiver_values = [[[False] * 3] * 32, [[False] * 3] * 32, [[False] * 3] * 32] + expected = [False] * 48 + actual = mapper.map_read("ANT_mask_RW", receiver_values) + numpy.testing.assert_equal(expected, actual) + + def test_ant_read_mask_r_hba_0_and_1_on_rcu_1_and_0_of_recv_1(self): + mapper = HBATToRecvMapper(self.control_hba_0_and_1_on_rcu_1_and_0_of_recv_1, self.power_not_connected, 3) + + receiver_values = [[False, True, False], [[False] * 3] * 31, [[False] * 3] * 32, [[False] * 3] * 32] + expected = [True, False] + [False] * 46 + actual = mapper.map_read("ANT_mask_RW", receiver_values) + + numpy.testing.assert_equal(expected, actual) + + def test_bf_read_delay_steps_r_no_mapping(self): + mapper = HBATToRecvMapper(self.control_not_connected, self.power_not_connected, 3) + + receiver_values = [[[0] * 32] * 96, [[0] * 32] * 96, [[0] * 32] * 96] + expected = [[0] * 32] * 48 + actual = mapper.map_read("HBAT_BF_delay_steps_R", receiver_values) + numpy.testing.assert_equal(expected, actual) + + def test_bf_read_delay_steps_r_hba_0_and_1_on_rcu_1_and_0_of_recv_1(self): + mapper = HBATToRecvMapper(self.control_hba_0_and_1_on_rcu_1_and_0_of_recv_1, self.power_not_connected, 3) + + receiver_values = [[[2] * 32, [1] * 32] + [[0] * 32] * 94, [[0] * 32] * 96, [[0] * 32] * 96] + expected = [[1] * 32, [2] * 32] + [[0] * 32] * 46 + actual = mapper.map_read("HBAT_BF_delay_steps_R", receiver_values) + + numpy.testing.assert_equal(expected, actual) + + def test_bf_read_delay_steps_rw_no_mapping(self): + mapper = HBATToRecvMapper(self.control_not_connected, self.power_not_connected, 3) + + receiver_values = [[[0] * 32] * 96, [[0] * 32] * 96, [[0] * 32] * 96] + expected = [[0] * 32] * 48 + actual = mapper.map_read("HBAT_BF_delay_steps_RW", receiver_values) + numpy.testing.assert_equal(expected, actual) + + def test_bf_read_delay_steps_rw_hba_0_and_1_on_rcu_1_and_0_of_recv_1(self): + mapper = HBATToRecvMapper(self.control_hba_0_and_1_on_rcu_1_and_0_of_recv_1, self.power_not_connected, 3) + + receiver_values = [[[2] * 32, [1] * 32] + [[0] * 32] * 94, [[0] * 32] * 96, [[0] * 32] * 96] + expected = [[1] * 32, [2] * 32] + [[0] * 32] * 46 + actual = mapper.map_read("HBAT_BF_delay_steps_RW", receiver_values) + + numpy.testing.assert_equal(expected, actual) + + def test_map_read_led_on_r_unmapped(self): + mapper = HBATToRecvMapper(self.control_not_connected, self.power_not_connected, 3) + + receiver_values = [[[False] * 32] * 96, [[False] * 32] * 96, [[False] * 32] * 96] + expected = [[False] * 32] * 48 + actual = mapper.map_read("HBAT_LED_on_R", receiver_values) + numpy.testing.assert_equal(expected, actual) + + def test_map_read_led_on_r_hba_0_and_1_on_rcu_1_and_0_of_recv_1(self): + mapper = HBATToRecvMapper(self.control_hba_0_and_1_on_rcu_1_and_0_of_recv_1, self.power_not_connected, 3) + + receiver_values = [[[False, True] * 16, [True, False] * 16] + [[False] * 32] * 94, [[False] * 32] * 96, [[False] * 32] * 96] + + expected = [[True, False] * 16, [False, True] * 16] + [[False] * 32] * 46 + actual = mapper.map_read("HBAT_LED_on_R", receiver_values) + numpy.testing.assert_equal(expected, actual) + + def test_map_read_led_on_rw_unmapped(self): + mapper = HBATToRecvMapper(self.control_not_connected, self.power_not_connected, 3) + + receiver_values = [[[False] * 32] * 96, [[False] * 32] * 96, [[False] * 32] * 96] + expected = [[False] * 32] * 48 + actual = mapper.map_read("HBAT_LED_on_RW", receiver_values) + numpy.testing.assert_equal(expected, actual) + + def test_map_read_led_on_rw_hba_0_and_1_on_rcu_1_and_0_of_recv_1(self): + mapper = HBATToRecvMapper(self.control_hba_0_and_1_on_rcu_1_and_0_of_recv_1, self.power_not_connected, 3) + + receiver_values = [[[False, True] * 16, [True, False] * 16] + [[False] * 32] * 94, [[False] * 32] * 96, [[False] * 32] * 96] + + expected = [[True, False] * 16, [False, True] * 16] + [[False] * 32] * 46 + actual = mapper.map_read("HBAT_LED_on_RW", receiver_values) + numpy.testing.assert_equal(expected, actual) + + def test_map_read_pwr_lna_on_r_unmapped(self): + mapper = HBATToRecvMapper(self.control_not_connected, self.power_not_connected, 3) + + receiver_values = [[[False] * 32] * 96, [[False] * 32] * 96, [[False] * 32] * 96] + expected = [[False] * 32] * 48 + actual = mapper.map_read("HBAT_PWR_LNA_on_R", receiver_values) + numpy.testing.assert_equal(expected, actual) + + def test_map_read_pwr_lna_on_r_hba_0_and_1_on_rcu_1_and_0_of_recv_1(self): + mapper = HBATToRecvMapper(self.control_hba_0_and_1_on_rcu_1_and_0_of_recv_1, self.power_not_connected, 3) + + receiver_values = [[[False, True] * 16, [True, False] * 16] + [[False] * 32] * 94, [[False] * 32] * 96, [[False] * 32] * 96] + + expected = [[True, False] * 16, [False, True] * 16] + [[False] * 32] * 46 + actual = mapper.map_read("HBAT_PWR_LNA_on_R", receiver_values) + numpy.testing.assert_equal(expected, actual) + + def test_map_read_pwr_lna_on_rw_unmapped(self): + mapper = HBATToRecvMapper(self.control_not_connected, self.power_not_connected, 3) + + receiver_values = [[[False] * 32] * 96, [[False] * 32] * 96, [[False] * 32] * 96] + expected = [[False] * 32] * 48 + actual = mapper.map_read("HBAT_PWR_LNA_on_RW", receiver_values) + numpy.testing.assert_equal(expected, actual) + + def test_map_read_pwr_lna_on_rw_hba_0_and_1_on_rcu_1_and_0_of_recv_1(self): + mapper = HBATToRecvMapper(self.control_hba_0_and_1_on_rcu_1_and_0_of_recv_1, self.power_not_connected, 3) + + receiver_values = [[[False, True] * 16, [True, False] * 16] + [[False] * 32] * 94, [[False] * 32] * 96, [[False] * 32] * 96] + + expected = [[True, False] * 16, [False, True] * 16] + [[False] * 32] * 46 + actual = mapper.map_read("HBAT_PWR_LNA_on_RW", receiver_values) + numpy.testing.assert_equal(expected, actual) + + def test_map_read_pwr_on_r_unmapped(self): + mapper = HBATToRecvMapper(self.control_not_connected, self.power_not_connected, 3) + + receiver_values = [[[False] * 32] * 96, [[False] * 32] * 96, [[False] * 32] * 96] + expected = [[False] * 32] * 48 + actual = mapper.map_read("HBAT_PWR_on_R", receiver_values) + numpy.testing.assert_equal(expected, actual) + + def test_map_read_pwr_on_r_hba_0_and_1_on_rcu_1_and_0_of_recv_1(self): + mapper = HBATToRecvMapper(self.control_hba_0_and_1_on_rcu_1_and_0_of_recv_1, self.power_not_connected, 3) + + receiver_values = [[[False, True] * 16, [True, False] * 16] + [[False] * 32] * 94, [[False] * 32] * 96, [[False] * 32] * 96] + + expected = [[True, False] * 16, [False, True] * 16] + [[False] * 32] * 46 + actual = mapper.map_read("HBAT_PWR_on_R", receiver_values) + numpy.testing.assert_equal(expected, actual) + + def test_map_read_pwr_on_rw_unmapped(self): + mapper = HBATToRecvMapper(self.control_not_connected, self.power_not_connected, 3) + + receiver_values = [[[False] * 32] * 96, [[False] * 32] * 96, [[False] * 32] * 96] + expected = [[False] * 32] * 48 + actual = mapper.map_read("HBAT_PWR_on_RW", receiver_values) + numpy.testing.assert_equal(expected, actual) + + def test_map_read_pwr_on_rw_hba_0_and_1_on_rcu_1_and_0_of_recv_1(self): + mapper = HBATToRecvMapper(self.control_hba_0_and_1_on_rcu_1_and_0_of_recv_1, self.power_not_connected, 3) + + receiver_values = [[[False, True] * 16, [True, False] * 16] + [[False] * 32] * 94, [[False] * 32] * 96, [[False] * 32] * 96] + + expected = [[True, False] * 16, [False, True] * 16] + [[False] * 32] * 46 + actual = mapper.map_read("HBAT_PWR_on_RW", receiver_values) + numpy.testing.assert_equal(expected, actual) + +# Rename to write + + def test_map_write_ant_mask_rw_no_mapping_and_one_receiver(self): + mapper = HBATToRecvMapper(self.control_not_connected, self.power_not_connected, 1) + + set_values = [False] * 48 + expected = [[[False] * 3] * 32] + actual = mapper.map_write("ANT_mask_RW", set_values) + numpy.testing.assert_equal(expected, actual) + + def test_map_write_ant_mask_rw_no_mapping_and_two_receivers(self): + mapper = HBATToRecvMapper(self.control_not_connected, self.power_not_connected, 2) + + set_values = [False] * 48 + expected = [[[False] * 3] * 32] * 2 + actual = mapper.map_write("ANT_mask_RW", set_values) + numpy.testing.assert_equal(expected, actual) + + def test_map_write_ant_mask_rw_hba_0_and_1_on_rcu_1_and_0_of_recv_1(self): + mapper = HBATToRecvMapper(self.control_hba_0_and_1_on_rcu_1_and_0_of_recv_1, self.power_not_connected, 1) + + set_values = [True, False] + [False] * 46 + expected = [[[False, True, False]] + [[False] * 3] * 31] + actual = mapper.map_write("ANT_mask_RW", set_values) + numpy.testing.assert_equal(expected, actual) + + def test_map_write_bf_delay_steps_rw_no_mapping_and_one_receiver(self): + mapper = HBATToRecvMapper(self.control_not_connected, self.power_not_connected, 1) + + set_values = [[1] * 32] * 48 + expected = [[[0] * 32] * 96] + actual = mapper.map_write("HBAT_BF_delay_steps_RW", set_values) + numpy.testing.assert_equal(expected, actual) + + def test_map_write_bf_delay_steps_rw_no_mapping_and_two_receivers(self): + mapper = HBATToRecvMapper(self.control_not_connected, self.power_not_connected, 2) + + set_values = [[1] * 32] * 48 + expected = [[[0] * 32] * 96, [[0] * 32] * 96] + actual = mapper.map_write("HBAT_BF_delay_steps_RW", set_values) + numpy.testing.assert_equal(expected, actual) + + def test_map_write_bf_delay_steps_rw_hba_0_and_1_on_rcu_1_and_0_of_recv_1(self): + mapper = HBATToRecvMapper(self.control_hba_0_and_1_on_rcu_1_and_0_of_recv_1, self.power_not_connected, 1) + + set_values = [[1] * 32, [2] * 32] + [[0] * 32] * 46 + expected = [[[2] * 32, [1] * 32] + [[0] * 32] * 94] + actual = mapper.map_write("HBAT_BF_delay_steps_RW", set_values) + numpy.testing.assert_equal(expected, actual) + + def test_map_write_led_on_rw_no_mapping_and_one_receiver(self): + mapper = HBATToRecvMapper(self.control_not_connected, self.power_not_connected, 1) + + set_values = [[False] * 32] * 48 + expected = [[[False] * 32] * 96] + actual = mapper.map_write("HBAT_LED_on_RW", set_values) + numpy.testing.assert_equal(expected, actual) + + def test_map_write_led_on_rw_no_mapping_and_two_receivers(self): + mapper = HBATToRecvMapper(self.control_not_connected, self.power_not_connected, 2) + + set_values = [[False] * 32] * 48 + expected = [[[False] * 32] * 96, [[False] * 32] * 96] + actual = mapper.map_write("HBAT_LED_on_RW", set_values) + numpy.testing.assert_equal(expected, actual) + + def test_map_write_led_on_rw_hba_0_and_1_on_rcu_1_and_0_of_recv_1(self): + mapper = HBATToRecvMapper(self.control_hba_0_and_1_on_rcu_1_and_0_of_recv_1, self.power_not_connected, 1) + + set_values = [[False, True] * 16, [True, False] * 16] + [[False] * 32] * 46 + expected = [[[True, False] * 16, [False, True] * 16] + [[0] * 32] * 94] + actual = mapper.map_write("HBAT_LED_on_RW", set_values) + numpy.testing.assert_equal(expected, actual) + + def test_map_write_pwr_lna_on_rw_no_mapping_and_one_receiver(self): + mapper = HBATToRecvMapper(self.control_not_connected, self.power_not_connected, 1) + + set_values = [[False] * 32] * 48 + expected = [[[False] * 32] * 96] + actual = mapper.map_write("HBAT_PWR_LNA_on_RW", set_values) + numpy.testing.assert_equal(expected, actual) + + def test_map_write_pwr_lna_on_rw_no_mapping_and_two_receivers(self): + mapper = HBATToRecvMapper(self.control_not_connected, self.power_not_connected, 2) + + set_values = [[False] * 32] * 48 + expected = [[[False] * 32] * 96, [[False] * 32] * 96] + actual = mapper.map_write("HBAT_PWR_LNA_on_RW", set_values) + numpy.testing.assert_equal(expected, actual) + + def test_map_write_pwr_lna_on_rw_hba_0_and_1_on_rcu_1_and_0_of_recv_1(self): + mapper = HBATToRecvMapper(self.control_hba_0_and_1_on_rcu_1_and_0_of_recv_1, self.power_not_connected, 1) + + set_values = [[False, True] * 16, [True, False] * 16] + [[False] * 32] * 46 + expected = [[[True, False] * 16, [False, True] * 16] + [[0] * 32] * 94] + actual = mapper.map_write("HBAT_PWR_LNA_on_RW", set_values) + numpy.testing.assert_equal(expected, actual) + + def test_map_write_pwr_on_rw_no_mapping_and_one_receiver(self): + mapper = HBATToRecvMapper(self.control_not_connected, self.power_not_connected, 1) + + set_values = [[False] * 32] * 48 + expected = [[[False] * 32] * 96] + actual = mapper.map_write("HBAT_PWR_on_RW", set_values) + numpy.testing.assert_equal(expected, actual) + + def test_map_write_lna_on_rw_no_mapping_and_two_receivers(self): + mapper = HBATToRecvMapper(self.control_not_connected, self.power_not_connected, 2) + + set_values = [[False] * 32] * 48 + expected = [[[False] * 32] * 96, [[False] * 32] * 96] + actual = mapper.map_write("HBAT_PWR_on_RW", set_values) + numpy.testing.assert_equal(expected, actual) + + def test_map_write_pwr_on_rw_hba_0_and_1_on_rcu_1_and_0_of_recv_1(self): + mapper = HBATToRecvMapper(self.control_hba_0_and_1_on_rcu_1_and_0_of_recv_1, self.power_not_connected, 1) + + set_values = [[False, True] * 16, [True, False] * 16] + [[False] * 32] * 46 + expected = [[[True, False] * 16, [False, True] * 16] + [[0] * 32] * 94] + actual = mapper.map_write("HBAT_PWR_on_RW", set_values) + numpy.testing.assert_equal(expected, actual) + diff --git a/tangostationcontrol/tangostationcontrol/test/devices/test_lofar_device.py b/tangostationcontrol/tangostationcontrol/test/devices/test_lofar_device.py index 46004707ea59c681015b987ce97adb26931a189a..15434810dd7bf9d3162ce64282661f3fa358b3de 100644 --- a/tangostationcontrol/tangostationcontrol/test/devices/test_lofar_device.py +++ b/tangostationcontrol/tangostationcontrol/test/devices/test_lofar_device.py @@ -52,3 +52,4 @@ class TestLofarDevice(base.TestCase): proxy.initialise() self.assertEqual(42.0, proxy.read_attribute_A) self.assertListEqual([42.0, 43.0], proxy.read_attribute_B_array.tolist()) + diff --git a/tangostationcontrol/tangostationcontrol/toolkit/archiver_base_ts.py b/tangostationcontrol/tangostationcontrol/toolkit/archiver_base_ts.py index fd6f8f7aa0f39d52f79798e89923e75c60de0b5e..801ba8634209b73918b54e6f3fb716563cb73b1b 100644 --- a/tangostationcontrol/tangostationcontrol/toolkit/archiver_base_ts.py +++ b/tangostationcontrol/tangostationcontrol/toolkit/archiver_base_ts.py @@ -154,6 +154,79 @@ class Lofar_Array_Ulong64(Lofar_Array_Attribute): class Lofar_Array_Ushort(Lofar_Array_Attribute): __tablename__ = 'lofar_array_ushort' value = Column(INTEGER) + +class Lofar_Image_Attribute(Base): + """ + Abstract Class that represents a Lofar customized Tango Attribute view + """ + __abstract__ = True + __table_args__ = {'extend_existing': True} + + data_time = Column(TIMESTAMP, primary_key=True) + device = Column(String, primary_key=True) + name = Column(String, primary_key=True) + x = Column(INTEGER, primary_key=True) + y = Column(INTEGER, primary_key=True) + + def __repr__(self): + return f"<Attribute(device='{self.device}', name='{self.name}', data_time='{self.data_time}',index_x='{self.x}',index_y='{self.y}',value='{self.value}'>" + +class Lofar_Image_Boolean(Lofar_Image_Attribute): + __tablename__ = 'lofar_image_boolean' + value = Column(Boolean) + +class Lofar_Image_Double(Lofar_Image_Attribute): + __tablename__ = 'lofar_image_double' + value = Column(FLOAT) + +class Lofar_Image_Encoded(Lofar_Image_Attribute): + __tablename__ = 'lofar_image_encoded' + value = Column(BYTEA) + +class Lofar_Image_Enum(Lofar_Image_Attribute): + __tablename__ = 'lofar_image_enum' + value = Column(INTEGER) + +class Lofar_Image_Float(Lofar_Image_Attribute): + __tablename__ = 'lofar_image_float' + value = Column(FLOAT) + +class Lofar_Image_Long(Lofar_Image_Attribute): + __tablename__ = 'lofar_image_long' + value = Column(INT4RANGE) + +class Lofar_Image_Long64(Lofar_Image_Attribute): + __tablename__ = 'lofar_image_long64' + value = Column(INT8RANGE) + +class Lofar_Image_Short(Lofar_Image_Attribute): + __tablename__ = 'lofar_image_short' + value = Column(INTEGER) + +class Lofar_Image_State(Lofar_Image_Attribute): + __tablename__ = 'lofar_image_state' + value = Column(INTEGER) + +class Lofar_Image_String(Lofar_Image_Attribute): + __tablename__ = 'lofar_image_string' + value = Column(TEXT) + +class Lofar_Image_Uchar(Lofar_Image_Attribute): + __tablename__ = 'lofar_image_uchar' + value = Column(INTEGER) + +class Lofar_Image_Ulong(Lofar_Image_Attribute): + __tablename__ = 'lofar_image_ulong' + value = Column(INTEGER) + +class Lofar_Image_Ulong64(Lofar_Image_Attribute): + __tablename__ = 'lofar_image_ulong64' + value = Column(INTEGER) + +class Lofar_Image_Ushort(Lofar_Image_Attribute): + __tablename__ = 'lofar_image_ushort' + value = Column(INTEGER) + # ----------------- ----------------- ----------------- # class Attribute(Base): @@ -777,6 +850,8 @@ def get_viewclass_by_tablename(tablename: str): return c elif format=='array' and c.__tablename__ == f"lofar_array_{datatype}": return c + elif format=='image' and c.__tablename__ == f"lofar_image_{datatype}": + return c return None def build_array_from_record(rows: List[Array], dim_x: int):