CHANGES.rst - mhvk/baseband

26 Aug.,2024

 

CHANGES.rst - mhvk/baseband

Bug fixes

  • Avoid numpy>=2.0 deprecation warnings being raised in the __array__() methods of Payload and Frame.__array__(). [#525]
  • The minimum versions required by baseband are now python 3.10, astropy 5.1, and numpy 1.24.

Bug Fixes

  • Ensure header equality tests work with numpy 2.0. [#523]
  • Ensure Mark4 header shapes always are a tuple of int (no numpy ints). [#523]
  • Avoid duplicated warnings about bad framesets. [#523]

Bug Fixes

  • GUPPI files written by the VEGAS backend, which, oddly, writes numbers as strings with quotes around them, can now be read. [#515]

Bug Fixes

  • DADA files such as those produced with Meerkat, in which the header ends without an # end of header comment line can now be read. [#500]
  • Modules of tasks are now properly accessible. E.g., with baseband-tasks available, from baseband.tasks.dm import DispersionMeasure works. [#508]

Bug Fixes

  • Ensure that Mark 5B files with extra bytes at the start (i.e., with a first frame that starts later) can be read. [#489]
  • Ensure that Mark 5B frames with corrupted headers are properly recognized and do not lead to exceptions. [#490]
  • Add some minimal validation for Mark 4 and Mark 5B FileReader arguments. [#490]

API Changes

  • The deprecated vlbi_base module has been removed. [#484]

Bug Fixes

  • Ensure that vdif files with 1 frame per second can be read. [#488]

Other Changes and Additions

  • Entry points are now handled via importlib.metadata (or the python 3.7 backport importlib_metadata), meaning baseband no longer requires the entrypoints package. [#477]

Bug Fixes

  • Ensure that pathlib.Path objects are recognized as valid in the various openers. [#467]
  • Raise a proper FileNotFoundError instead of an obscure AttributeError if trying to get file_info on a non-existing file. [#467]
  • Pass on all "irrelevant" arguments not understood by file_info to the general opener, so they can be used or raise TypeError in a place where it will be clearer why that happens. [#468]
  • Support for VDIF EDV3 data with payload size of bytes. [#456]

Bug Fixes

  • Fix the GUPPIHeader class incorrectly ignoring the STT_OFFS header keyword. [#457]

Bug Fixes

  • Allow the GUPPI reader to assume channel-first ordering by default, i.e., no longer insist that PKTFMT is one of '1SFA' or 'SIMPLE'. Instead, info will include a warning for formats not known to work. [#453]
  • The minimum versions required by baseband are now python 3.7, numpy 1.17 and astropy 4.0.
  • Baseband now requires the (very small) entrypoints package.

New Features

  • Baseband now provides an baseband.io entry point, which allows other packages to make new readers accessible to baseband by defining an entry point in their setup.cfg. [#418]
  • Similarly, baseband also provides an baseband.tasks entry point, which allows other packages to define tasks useful for processing baseband data by defining an entry point in their setup.cfg. This is primarily intended for the future baseband-tasks package. [#445]

API Changes

The internals of baseband have undergone fairly substantial refactoring to make the classes more coherent. This should not affect users directly, but may affect those that have built their own readers.

Please visit our website for more information on this topic.

  • Following python 3.9, HeaderParser instances (which are subclasses of dict), can now be merged together using the | operator. For backward compatibility, using the + operator will remain supported. [#424]
  • All StreamWriters now require an explicit header0 to be passed in (as was already the case for DADA and GUPPI). Creation of a header0 from keyword arguments is now done inside the opener. [#417]
  • The vlbi_base module has been deprecated in favour of base, and VLBI prefixes of classes have been removed where these were not specific to actual VLBI data, leaving only VLBIHeaderBase, VLBIFileReaderBase, and VLBIStreamReaderBase. [#425]
  • The stream base classes will now try to get information that is not passed in explicitly from header0. Given this change, the keyword argument unsliced_shape become somewhat illogical, so was changed to sample_shape (still referring to the pre-squeeze and subset shape) [#415, #433]
  • Support for memory mapping of payloads has been moved into the base PayloadBase and FrameBase classes and thus is available for all formats. [#427]
  • Payloads and frames now all take sample_shape as an argument, instead of some taking nchan. [#429]

Bug Fixes

  • Extraneous arguments to stream writers are no longer ignored, but give rise to a TypeError. [#417]
  • The GUPPI stream reader now will include any overlap samples from the last frame. [#431]

Other Changes and Additions

  • All baseband formats now support passing in template strings for stream readers and writers (e.g., '{file_nr:07d}.vdif'). [#417]
  • The headers for VDIF and Mark 4 now expose standard complex_data and sample_shape properties, to match what is done for the other headers. Mark 5B headers expose only complex_data, as the sample shape cannot be inferred from the header. [#414, #428]
  • General classes to help writing open and info functions are now provided in baseband.vlbi_base.FileOpener and FileInfo. [#418]
  • The general open and file_open functions are now defined in baseband.io (but still imported at the top level). They are able to use any format defined via the plugin system. [#444]

Bug Fixes

  • For GSB phased data, fix the interpretation of sample_rate in calculating payload_nbytes. [#410]
  • Fix pickling for GSB phased data.

New Features

  • All file and stream readers can now be pickled. Writers still cannot, since those do not allow appending. [#395]

Bug Fixes

  • Mark 4 data written with the non-standard channel assignment used at Ft can now be read and written. [#380]
  • For GSB phased data, the default payload_nbytes has now been corrected so that it is always 4 MiB. [#401]
  • For GSB phased data, the sample_rate argument is now correctly interpreted as the rate of complete samples (previously, the number of channels were ignored). [#401]

Other Changes and Additions

  • The temporary_offset context manager of file readers now allows to pass in a possible initial offset to go to. [#390]
  • The GSB stream reader .info has been updated to include a consistency check of the size of the raw files with the number of frames inferred from the timestamp file. [#407]

Bug Fixes

  • Mark 5B is fixed so that writing files is now also possible on big-endian architectures.

Bug Fixes

  • Frame rates are now calculated correctly also for Mark 4 data in which the first frame is the last within a second. [#341]
  • Fixed a bug where a VDIF header was not found correctly if the file pointer was very close to the start of a header already. [#346]
  • In VDIF header verification, include that the implied payload must have non-negative size. [#348]
  • Mark 4 now checks by default (verify=True) that frames are ordered correctly. [#349]
  • find_header will now always check that the frame corresponding to a header is complete (i.e., fits within the file). [#354]
  • The count argument to .read() no longer is changed in-place, making it safe to pass in array scalars or dimensionless quantities. [#373]

Other Changes and Additions

  • The Mark 4, Mark 5B, and VDIF stream readers are now able to replace missing pieces of files with zeros using verify='fix'. This is also the new default; use verify=True for the old behaviour of raising an error on any inconsistency. [#357]
  • The VDIFFileReader gained a new get_thread_ids() method, which will scan through frames to determine the threads present in the file. This is now used inside VDIFStreamReader and, combined with the above, allows reading of files that have missing threads in their first frame set. [#361]
  • The stream reader info now also checks whether streams are continuous by reading the first and last sample, allowing a simple way to check whether the file will likely pose problems before possibly spending a lot of time reading it. [#364]
  • Much faster localization of Mark 5B frames. [#351]
  • VLBI file readers have gained a new method locate_frames that finds frame starts near the current location. [#354]
  • For VLBI file readers, find_header now raises an exception if no frame is found (rather than return None).
  • The Mark 4 file reader's locate_frame has been deprecated. Its functionality is replaced by locate_frames and find_header. [#354]
  • Custom stream readers can now override only part of reading a given frame and testing that it is the right one. [#355]
  • The HeaderParser class was refactored and simplified, making setting keys faster. [#356]
  • info now also provides the number of frames in a file. [#364]
  • This version only supports python3.

New Features

  • File information now includes whether a file can be read and decoded. The readable() method on stream readers also includes whether the data in a file can be decoded. [#316]

Bug Fixes

  • Empty GUPPI headers can now be created without having to pass in verify=False. This is needed for astropy 3.2, which initializes an empty header in its revamped .fromstring method. [#314]
  • VDIF multichannel headers and payloads are now forced to have power-of-two bits per sample. [#315]
  • Bits per complete sample for VDIF payloads are now calculated correctly also for non power-of-two bits per sample. [#315]
  • Guppi raw file info now presents the correct sample rate, corrected for overlap. [#319]
  • All headers now check that samples_per_frame are set to possible numbers. [#325]
  • Getting .info on closed files no longer leads to an error (though no information can be retrieved). [#326]

Other Changes and Additions

  • Increased speed of VDIF stream reading by removing redundant verification. Reduces the overhead for verification for VDIF CHIME data from 50% (factor 1.5) to 13%. [#321]
  • VDIF and Mark 5B readers and writers now support 1 bit per sample. [#277, #278]

Bug Fixes

  • VDIF reader will now properly ignore corrupt last frames. [#273]
  • Mark5B reader more robust against headers not being parsed correctly in Mark5BFileReader.find_header. [#275]
  • All stream readers now have a proper dtype attribute, not a corresponding np.float32 or np.complex64. [#280]
  • GUPPI stream readers no longer emit warnings on not quite FITS compliant headers. [#283]

Other Changes and Additions

  • Added release procedure to the documentation. [#268]

New Features

  • Expanded support for acccessing sequences of files to VLBI format openers and baseband.open. Enabled baseband.guppi.open to open file sequences using string templates like with baseband.dada.open. [#254]
  • Created baseband.helpers.sequentialfile.FileNameSequencer, a general-purpose filename sequencer that can be passed to any format opener. [#253]

Other Changes and Additions

  • Moved the Getting Started section to

    :ref:`"Using Baseband" <using_baseband>`

    , and created a new quickstart tutorial under

    :ref:`Getting Started <getting_started>`

    to better assist new users. [#260]

Bug Fixes

  • Ensure gsb times can be decoded with astropy-dev (which is to become astropy 3.1). [#249]
  • Fixed rounding error when encoding 4-bit data using baseband.vlbi_base.encoding.encode_4bit_base. [#250]
  • Added GUPPI/PUPPI to the list of file formats used by baseband.open and baseband.file_info. [#251]

New Features

  • Added a new baseband.file_info function, which can be used to inspect data files. [#200]
  • Added a general file opener, baseband.open which for a set of formats will check whether the file is of that format, and then load it using the corresponding module. [#198]
  • Allow users to pass a verify keyword to file openers reading streams. [#233]
  • Added support for the GUPPI format. [#212]
  • Enabled baseband.dada.open to read streams where the last frame has an incomplete payload. [#228]

API Changes

  • In analogy with Mark 5B, VDIF header time getting and setting now requires a frame rate rather than a sample rate. [#217, #218]
  • DADA and GUPPI now support passing either a start_time or offset (in addition to time) to set the start time in the header. [#240]

Bug Fixes

Other Changes and Additions

  • The baseband.data module with sample data files now has an explicit entry in the documentation. [#198]
  • Increased speed of VLBI stream reading by changing the way header sync patterns are stored, and removing redundant verification steps. VDIF sequential decode is now 5 - 10% faster (depending on the number of threads). [#241]

Bug Fixes

  • Fixed a bug in baseband.dada.open where passing a squeeze setting is ignored when also passing header keywords in 'ws' mode. [#211]
  • Raise an exception rather than return incorrect times for Mark 5B files in which the fractional seconds are not set. [#216]

Other Changes and Additions

  • Fixed broken links and typos in the documentation. [#211]
  • Initial release.

Baseband Processing - Navipedia


Receivers

Title Baseband Processing Edited by GMV Level Advanced Year of Publication

The baseband processing block is responsible for processing the down-converted and digitized GNSS signal in order to provide observables: code pseudo-ranges and carrier phase measurements, as well as navigation data. Additional information, such as Doppler frequency, Carrier-to-Noise ratio, or lock indicators, can also be provided.

In most GNSS receivers&#; architectures, the baseband processing relies on independent channels that track each satellite signal autonomously. Then, the information from each channel is integrated to derive a navigation solution.

Block Diagram

The baseband processing block is usually replicated over several channels. Each channel processes a given signal from a given satellite in order to provide GNSS observables and navigation data. A generic diagram of a single channel within the baseband processing block is depicted in the following figure.

Figure 1: Generic diagram of a single channel in the Baseband Processing block.

Generic diagram of a single channel in the Baseband Processing block.

The incoming signal is first stripped of its Doppler frequency (according to the current estimation), and then correlated with one (or more) PRN code replicas generated locally (according to the current estimation of code delay). Then new estimations of the Doppler frequency and code delay are computed based on the assessment of the correlation outputs. Note that for GNSS using FDMA modulation schemes, such as GLONASS, the Doppler removal block represents a shift in the frequency, not only corresponding to the estimated Doppler shift caused by the relative motion between the satellite and the user, but also to the centre frequency of the satellite signal at hand.

This process is implemented in an iterative way, usually using PLL and DLL tracking loops to track the incoming signal's phase and code delay, respectively. In parallel, the receiver extracts the navigation data of the incoming signal, and ensures monitoring and control features further described here.

Please refer to [1] for additional literature on baseband processing.

Principle

Figure 2: The correlation process: the receiver shifts the code replica (by changing

[math]\displaystyle{ \Delta t\, }[/math]

) until it finds a peak in the correlation output[2].

The correlation process: the receiver shifts the code replica (by changing) until it finds a peak in the correlation output

The basic principle of GNSS baseband processing relies on the correlation process. The underlying idea is that GNSS signals convey ranging codes that are built in a way that:

  • When the code is correlated with an aligned replica of itself, the correlation output is maximum: high auto-correlation properties.
  • When the code is correlated with a non-aligned replica of itself, the correlation output is low.
  • When the code is correlated with another code of the same family, the correlation output is low: low cross-correlation properties.

The basic principle is illustrated in Figure 2. The receiver first assigns each channel with a PRN code: for GNSS based on CDMA (such as GPS and Galileo), each satellite transmits a dedicated PRN code, whereas for GNSS based on FDMA (such as GLONASS), the PRN code is the same for all satellites.

At channel level, the incoming signal is correlated with the local replica of this PRN code over time. This local replica is generated in such a way that its code delay and phase characteristics vary, representing a two-dimensional search over code and (Doppler) frequency. Whenever the local replica parameters (code and frequency) match those of the incoming signal, the correlation output will reach a maximum, and the receiver will consider this pair (code and frequency) as the current estimates for these parameters.

Real-life systems are very noisy and dynamic: as a consequence, the auto-correlation peak seems to fluctuate at each time instant. This fact justifies the need for tracking loops (accumulators and filters) to continuously &#;follow&#; the incoming signal, since an error of 1 ms in the code delay would lead to an error of around 300 km in the pseudorange measurement.

Mathematical Model

At the output of the RF section, the baseband signal can be written as (neglecting noise):


[math]\displaystyle{ s_{BB} (t_k) = \Re\{s(t_k) exp[j\phi (t_k)]\}\, }[/math]


with [math]\displaystyle{ s(t_k) = A_Im_I(t_k)d(t_k)+jA_Qm_Q(t_k)d(t_k)\, }[/math]


where:

  • [math]\displaystyle{ A\, }[/math]

    is the signal amplitude.
  • [math]\displaystyle{ m(t_k)\, }[/math]

    = ±1, and it includes the PRN code and modulation information (e.g. subcarrier code for BOC modulations). For the case of GPS L1,

    [math]\displaystyle{ m_I(t_k)\, }[/math]

    and

    [math]\displaystyle{ m_Q(t_k)\, }[/math]

    are the C/A code and the P code respectively.
  • [math]\displaystyle{ d(t_k)\, }[/math]

    is the navigation data. Note that if no data is transmitted (e.g. pilot channels), this term is always 1.
  • [math]\displaystyle{ I\, }[/math]

    and

    [math]\displaystyle{ Q\, }[/math]

    refer to the In-phase and the Quadrature components, respectively.
  • [math]\displaystyle{ \phi (t_k)\, }[/math]

    accounts for the phase information, including Doppler frequency, receiver clock instabilities and initial signal phase.


The Doppler removal block rotates the complex baseband signal by the current estimation of the carrier phase:


[math]\displaystyle{ s_{out} (t_k) = s(t_k) exp[j\phi_e (t_k)]\}\, }[/math]


where:

  • the phase error is given by

    [math]\displaystyle{ \phi_e (t_k)=\hat{\phi}(t_k)-\phi(t_k)\, }[/math]

  • [math]\displaystyle{ \hat{\phi}(t_k)\, }[/math]

    is the phase estimated at the receiver.
  • [math]\displaystyle{ \phi(t_k)\, }[/math]

    is the phase of the incoming signal.


The correlation between the incoming signal and the local (PRN) code replica corresponds to the correlators and accumulators blocks, and can be written as:


[math]\displaystyle{ Corr_{out}(\hat{\tau}) = \sum_T s_{out}^{*} (t_k) m(t_k-\hat{\tau})\, }[/math]


where:

  • [math]\displaystyle{ T\, }[/math]

    is the integration time, i.e. the accumulation interval.
  • [math]\displaystyle{ \hat{\tau}\, }[/math]

    is the code delay estimated at the receiver.


As an example, the correlation output for a receiver tracking GPS L1 C/A code would be:


[math]\displaystyle{ Corr_{out}(\hat{\tau}) = \sum_T (A_Id(t_k)c_I(t_k) + jA_Qd(t_k)c_Q(t_k)) exp[-j\phi_e(t_k)] c_I(t_k - \hat{\tau})\, }[/math]

HUAXUN Product Page


where:

  • [math]\displaystyle{ c_I\, }[/math]

    would stand for C/A code.
  • [math]\displaystyle{ c_Q\, }[/math]

    would stand for the P code.
  • [math]\displaystyle{ c_I(t_k - \hat{\tau})\, }[/math]

    is the local replica generated at the receiver.


Considering that the cross-correlation between C/A code and P code can be neglected, the expression can be simplified to:


[math]\displaystyle{ Corr_{out}(\hat{\tau}) = \sum_T A_Id(t_k)c_I(t_k) exp[-j\phi_e(t_k)] c_I(t_k - \hat{\tau})\, }[/math]


Hence the output of the correlation and accumulation blocks can be written in its in-phase ([math]\displaystyle{ I\, }[/math]) and quadrature ([math]\displaystyle{ Q\, }[/math]) components as:


[math]\displaystyle{ I_P = A_I d_I R_{c_I} (\tau_e) cos(\phi_e)\, }[/math]

[math]\displaystyle{ Q_P = -A_Id_IR_{c_I}(\tau_e)sin(\phi_e)\, }[/math]


where:

  • [math]\displaystyle{ R_{c_I}\, }[/math]

    is the auto-correlation of the

    [math]\displaystyle{ c_I\, }[/math]

    code.
  • [math]\displaystyle{ P\, }[/math]

    stands for the Prompt replica, i.e. the replica generated at the receiver which is aligned with the incoming signal.
  • [math]\displaystyle{ \tau_e\, }[/math]

    is the error of the code delay estimated at the receiver.
  • [math]\displaystyle{ \phi_e\, }[/math]

    is the error of the carrier phase estimated at the receiver.


The correlation outputs are then fed to the tracking loops. Further information about GNSS receiver correlators can be found here.

Signal Detection

In the previous sections it's assumed that a current estimate of the parameter pair {code delay, Doppler frequency} was already available, and therefore the receiver would generate the local PRN code replica using those parameter estimates. However, when a channel is first set up, these estimates are usually not available, and therefore each channel will launch a cold search for the signal. The channel is said to be in Acquisition mode when searching for signals, and then transits to Tracking mode once the signal is found. For further information on receiver operations, see here.

When in acquisition mode, each channel is looking for all possible pairs of {code delay, Doppler frequency}, by generating a set of possible local replicas, and correlating each of them with the incoming signal. The power of the correlation output is somehow a measure of how good (or how close to the real signal) the estimates of the code delay and carrier phase are. Nevertheless, the correlation outputs described up to now are all based on the assumption that the signals are not subject to noise: in fact, the signal is actually so noisy that a single shot decision process on the correlation results has a very low probability of detecting the presence of a signal.

The presence of a signal is assessed using the following decision statistic:


[math]\displaystyle{ z= \sum_{k=1}^M |Y(k)|^2\, }[/math]


where:

  • [math]\displaystyle{ M\, }[/math]

    is the number of non-coherent integrations.
  • [math]\displaystyle{ Y\, }[/math]

    is the output of the correlation of the incoming signal with the local replica (complex) over the coherent integration time

    [math]\displaystyle{ T\, }[/math]

    .
  • [math]\displaystyle{ k\, }[/math]

    refers to the

    [math]\displaystyle{ k\, }[/math]

    th coherent integration interval.


Figure 3:Example of the correlation output for different code delays using 1 coherent and 1 non-coherent integration times.

Example of the correlation output for different code delays using 1 coherent and 1 non-coherent integration times.

The statistic [math]\displaystyle{ z\, }[/math] is then compared to a detection threshold, to assess whether the signal is present or not. This detection threshold is defined according to a target probability of false alarm.

In this process, the correlation outputs are actually integrated over time before further processing: this is gathered in the block I&D, standing for Integrate and Dump, in Figure 1. Two types of integration can be considered:

  • Coherent integrations: this technique consists in using longer integration times (multiples of the code length) before dumping the correlation output. Although this technique reduces noise, its performance is limited by the bit boundaries that carry the navigation message: in fact, if the integration time is such that it straddles the bit boundaries, then the correlation output is bound to lose overall power.
  • Non-coherent integrations: this technique consists in adding the results of single shot correlations together, before entering the decision process.

Due to squaring losses, the first technique is more effective that the latter, although limited by the navigation bit boundaries. As an example, Figure 3 represents the correlation outputs for all possible code delays in one single Doppler frequency (which we know beforehand to be the correct one). In this case, one coherent and one non-coherent integration were considered.

Because of the noisy conditions of GNSS signals, it is clear that the correlation peak is not visible from these results. To illustrate further, Figure 4 shows the correlation results for the same signal with different coherent (T) and non-coherent (M) integration times. The power increase becomes clear as M and T increase, hence showing clearly the correlation peak. The abscise value can be processed to yield a first estimate of the code delay.

Figure 4:Example of the correlation output for different code delays using different coherent and non-coherent integration times.

Example of the correlation output for different code delays using different coherent and non-coherent integration times.

On one hand, the examples shown in Figure 4 illustrates how coherent integrations bring more benefits (higher power and lower noise floor) when compared to non-coherent integrations, which need to cope with squaring losses. On the other hand, non-coherent integrations are safer to implement (before bit synchronization), since they do not need to take into account the straddling navigation bit boundaries.

Finally, the dimensioning of these techniques has to consider the expected noise conditions, probability of false alarm, probability of detection and mean acquisition time. In fact, the longer the integrations the higher probability of detection (the lower the probability of false alarm) but the slower the potential acquisition time is, since the receiver will dwell longer in each search bin, defined by the pair [code delay, Doppler frequency].

The concepts of coherent and non-coherent integrations are also used when the receiver is in tracking mode, in order to reduce the noise levels and increase accuracy (see tracking loops for details). The advantage is that, after bit synchronization, the receiver may increase the integration time up to the bit duration (e.g. 20 ms for GPS L1 C/A[3]). Modernized GNSS such as Galileo already have so-called pilot channels that do not convey any navigation data, and therefore can be used to further extend (coherent) integration times.

Long integration times are also used to track weak signals in harsh environments (such as indoors), but they are limited both by the data bit and by the accuracy of the clock/Doppler frequency estimation[4].

Platforms

Most receivers implement the baseband processing algorithms in dedicated ASICs, DSPs or even FPGAs, as discussed here. The baseband processing block, however, may also be implemented in software, following a Software Defined Radio (SDR)[5] philosophy. Although the software implementations provide a higher degree of flexibility, upgradeability and expandability, they still raise concerns regarding processing load and CPU power consumption in current commercial platforms[6].

References

  1. ^

    "GPS Data Processing: Code and Phase Algorithms, Techniques and Recipes", M. Hernandéz, J. Zomoza, J. Sanz, gAGE-NAV, gAGE/ UPC

  2. ^

    J. Sanz, J. Zornoza, M. Hernández, &#;Global Navigation Satellite Systems: Volume I: fundamentals and Algorithms&#;

  3. ^

    [GPS ICD 200, ] IS-GPS-200 Revision D, IRN-200D-001:NAVSTAR GLOBAL POSITIONING SYSTEM Navstar GPS Space Segment/Navigation User Interface, dated 7 March .

  4. ^

    DINGPOS: A Hybrid Indoor Navigation Platform for GPS and GALILEO, J. A. López-Salcedo (UAB) , Y. Capelle (TAS-F), M. Toledo (GMV), G. Seco (UAB), J. López Vicario (UAB), D. Kubrak (TAS-F), M. Monnerat (TAS-F), A. Mark (GMV), D. Jiménez (ESA), ION GNSS .

    The company is the world’s best NEW AND USED BASEBAND supplier. We are your one-stop shop for all needs. Our staff are highly-specialized and will help you find the product you need.

  5. ^ Software GNSS Receiver in Wikipedia
  6. ^ http://www.gpsworld.com