Skip to content
Snippets Groups Projects
Commit c7499371 authored by Jan David Mol's avatar Jan David Mol
Browse files

Merge branch 'L2SS-761-add-geo-coords' into 'master'

L2SS-761: Add geo (lat/long) coordinates to antennas to be able to draw them on a map

Closes L2SS-761

See merge request !310
parents 4b01b068 3a951a30
No related branches found
No related tags found
1 merge request!310L2SS-761: Add geo (lat/long) coordinates to antennas to be able to draw them on a map
...@@ -13,3 +13,4 @@ docker >= 5.0.3 # Apache 2 ...@@ -13,3 +13,4 @@ docker >= 5.0.3 # Apache 2
python-logstash-async >= 2.3.0 # MIT python-logstash-async >= 2.3.0 # MIT
python-casacore >= 3.3.1 # LGPLv3 python-casacore >= 3.3.1 # LGPLv3
etrs-itrs@git+https://github.com/brentjens/etrs-itrs # license pending etrs-itrs@git+https://github.com/brentjens/etrs-itrs # license pending
lofarantpos >= 0.5.0 # Apache 2
import etrsitrs import etrsitrs
import lofarantpos.geo
import numpy import numpy
import math
""" """
LOFAR station positions are measured in ETRS89, which are the coordinates of the position as it would be in 1989. 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 ...@@ -16,18 +18,34 @@ import numpy
The ETRSitrs package does all the transformation calculations for us. 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: 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. """ """ Convert an array of coordinate triples from ETRS to ITRF, in the given reference frame and epoch. """
# fetch converter # fetch converter
ETRS_to_ITRF_fn = etrsitrs.convert_fn("ETRF2000", ITRF_reference_frame, ITRF_reference_epoch) ETRS_to_ITRF_fn = etrsitrs.convert_fn("ETRF2000", ITRF_reference_frame, ITRF_reference_epoch)
if ETRS_coordinates.ndim == 1: return _apply_fn_on_one_element_or_array(ETRS_to_ITRF_fn, ETRS_coordinates)
# convert a single coordinate triple
ITRF_coordinates = ETRS_to_ITRF_fn(ETRS_coordinates) def ETRS_to_GEO(ETRS_coordinates: numpy.array) -> numpy.array:
else: """ Convert an array of coordinate triples from ETRS to latitude/longitude (degrees). """
# convert each coordinate triple
ITRF_coordinates = numpy.apply_along_axis(ETRS_to_ITRF_fn, 1, ETRS_coordinates) 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 # Geo coordinates are only used for rough positioning. The difference between ITRF and ETRS matters little here
return ITRF_coordinates ITRF_to_GEO = ETRS_to_GEO
...@@ -22,7 +22,7 @@ from math import pi ...@@ -22,7 +22,7 @@ from math import pi
# Additional import # Additional import
from tangostationcontrol.beam.hba_tile import HBATAntennaOffsets 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.entrypoint import entry
from tangostationcontrol.common.lofar_logging import device_logging_to_python from tangostationcontrol.common.lofar_logging import device_logging_to_python
from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper
...@@ -234,6 +234,10 @@ class RECV(opcua_device): ...@@ -234,6 +234,10 @@ class RECV(opcua_device):
doc='Absolute reference position of antenna field, in ITRF', doc='Absolute reference position of antenna field, in ITRF',
dtype=(numpy.float,), max_dim_x=3) 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, HBAT_antenna_ITRF_offsets_R = attribute(access=AttrWriteType.READ,
doc='Offsets of the antennas within a tile, in ITRF ("iHBADeltas"). True shape: 96x16x3.', 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) dtype=((numpy.float,),), max_dim_x=48, max_dim_y=96)
...@@ -242,6 +246,10 @@ class RECV(opcua_device): ...@@ -242,6 +246,10 @@ class RECV(opcua_device):
doc='Absolute reference position of each tile, in ITRF', doc='Absolute reference position of each tile, in ITRF',
dtype=((numpy.float,),), max_dim_x=3, max_dim_y=96) 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): def read_Antenna_Field_Reference_ITRF_R(self):
# provide ITRF field coordinates if they were configured # provide ITRF field coordinates if they were configured
if self.Antenna_Field_Reference_ITRF: if self.Antenna_Field_Reference_ITRF:
...@@ -251,6 +259,9 @@ class RECV(opcua_device): ...@@ -251,6 +259,9 @@ class RECV(opcua_device):
ETRS_coordinates = numpy.array(self.Antenna_Field_Reference_ETRS).reshape(3) 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) 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): def read_HBAT_antenna_ITRF_offsets_R(self):
base_antenna_offsets = numpy.array(self.HBAT_base_antenna_offsets).reshape(16,3) base_antenna_offsets = numpy.array(self.HBAT_base_antenna_offsets).reshape(16,3)
PQR_to_ETRS_rotation_matrix = numpy.array(self.HBAT_PQR_to_ETRS_rotation_matrix).reshape(3,3) PQR_to_ETRS_rotation_matrix = numpy.array(self.HBAT_PQR_to_ETRS_rotation_matrix).reshape(3,3)
...@@ -274,6 +285,9 @@ class RECV(opcua_device): ...@@ -274,6 +285,9 @@ class RECV(opcua_device):
ETRS_coordinates = numpy.array(self.HBAT_reference_ETRS).reshape(96,3) 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) 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 # Summarising Attributes
# ---------- # ----------
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
# Distributed under the terms of the APACHE license. # Distributed under the terms of the APACHE license.
# See LICENSE.txt for more info. # 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 from tangostationcontrol.test import base
...@@ -41,3 +41,32 @@ class TestETRS_to_ITRF(base.TestCase): ...@@ -41,3 +41,32 @@ class TestETRS_to_ITRF(base.TestCase):
LOFAR1_CS001_LBA_ITRF = [3826923.50275, 460915.488115, 5064643.517] 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) 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)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment