Teslabs Engineering Teslabs Engineering is a company offering a wide range of Software and Electrical Engineering services. https://teslabs.com/ Mon, 13 May 2024 10:34:23 +0000 Mon, 13 May 2024 10:34:23 +0000 Jekyll v3.9.5 A way to calibrate a magnetometer <p>My first contact with a digital magnetometer dates back to the summer of 2009. At that time, as many <em>arduinoers</em>, I was more concerned about coding the I2C/SPI driver other than understanding the device itself. A few months later I got an iPhone 4 and realized that the compass application, which used the internal phone magnetometer, required calibration every time it had to be used. And so, I became interested on why was this step required and how can it be done. In this post I will try to explain, together with a practical example, a way to do it. It is in essence the method described <a href="https://sites.google.com/site/sailboatinstruments1/">here</a>, but with some more details and references.</p> <figure class="text-center"> <img src="/content/posts/magnetometer-calibration/magnetometer.png" style="" alt="Magnetometer" /> <figcaption>A digital triple-axis magnetometer sold nowadays. Source: <a href="http://www.sparkfun.com">Sparkfun (CC-BY-NC-SA)</a></figcaption> </figure> <h1 id="preliminary-concepts">Preliminary concepts</h1> <p>Before we start it is important to have a few concepts clear. It seems logical that we need to have a basic understanding of the Earth magnetic field, as it is what we want to measure. Furthermore, basic knowledge of quadrics (e.g. spheres or ellipsoids) will turn out to be important. It may seem awkward to talk about quadrics here, but it will quickly make sense.</p> <h2 id="the-earth-magnetic-field">The Earth magnetic field</h2> <p>At any location on Earth, the magnetic field can be locally represented as a constant three dimensional vector (\(\mathbf{h}_0\)). This vector can be characterized by three properties. Firstly, the intensity or magnitude (\(\mathcal{F}\)), normally measured in nanoteslas (nT) with an approximate range of 25000 nT to 65000 nT. Secondly, the inclination (\(\mathcal{I}\)), with negative values (up to -90°) if pointing up and positive (up to 90°) if pointing down. Thirdly, the declination (\(\mathcal{D}\)) which measures the deviation of the field relative to the geographical north and is positive eastward.</p> <figure> <img src="/content/posts/magnetometer-calibration/mfc.png" alt="Earth Magnetic Field Coordinates" /> <figcaption>Magnetic field frame of reference. Source: <a href="https://commons.wikimedia.org/wiki/File%3AXYZ-DIS_magnetic_field_coordinates.svg">Wikimedia Commons</a></figcaption> </figure> <p>Using the frame of reference in the figure shown above, the Earth magnetic field vector \(\mathbf{h}_0\) can be written as:</p> \[\mathbf{h}_0 = \mathcal{F} \begin{bmatrix} \cos{(\mathcal{I})} \cos{(\mathcal{D})} \\ \cos{(\mathcal{I})} \sin{(\mathcal{D})} \\ \sin{(\mathcal{I})} \end{bmatrix}. \label{eq_h0} \tag{1}\] <p>Given a geographical point the <a href="https://www.ngdc.noaa.gov/geomag/WMM/DoDWMM.shtml">WMM model</a> can be used to obtain the expected values for \(\mathcal{F}\), \(\mathcal{I}\) and \(\mathcal{D}\). The magnetic declination is specially useful in order to calculate the geographical north in a compass.</p> <figure> <div class="grid"> <div class="grid-col-33"> <a class="zoom-in" href="/content/posts/magnetometer-calibration/WMM2015_F_MERC.jpg"> <img src="/content/posts/magnetometer-calibration/WMM2015_F_MERC.jpg" alt="WMM (F) 2015" /> </a> </div> <div class="grid-col-33"> <a class="zoom-in" href="/content/posts/magnetometer-calibration/WMM2015_I_MERC.jpg"> <img src="/content/posts/magnetometer-calibration/WMM2015_I_MERC.jpg" alt="WMM (I) 2015" /> </a> </div> <div class="grid-col-33"> <a class="zoom-in" href="/content/posts/magnetometer-calibration/WMM2015_D_MERC.jpg"> <img src="/content/posts/magnetometer-calibration/WMM2015_D_MERC.jpg" alt="WMM (D) 2015" /> </a> </div> </div> <figcaption>WMM 2015.0 intensity, inclination and declination maps. Source: NOAA/NGDC &amp; CIRES</figcaption> </figure> <h2 id="quadrics">Quadrics</h2> <p>Quadrics are all surfaces that can be expressed as a second degree polynomial in \(x\), \(y\) and \(z\). They include popular surfaces such as spheres, ellipsoids, paraboloids, etc. The general implicit equation of a quadric surface \(\mathit{S}\) is given by:</p> \[\begin{align} \mathit{S} :~ &amp; ax^2 + by^2 + cz^2 + \\ &amp; 2fyz + 2gxz + 2hxy + \\ &amp; px + qy + rz + d = 0. \end{align} \label{eq_quad_gen} \tag{2}\] <p>Eq. \(\eqref{eq_quad_gen}\) can be written in a semi-matricial form, which will be particularly useful for our problem:</p> \[\mathit{S}: \mathbf{x}^T \mathbf{M} \mathbf{x} + \mathbf{x}^T \mathbf{n} + d = 0 \label{eq_quad_semat} \tag{3}\] <p>where \(\mathbf{x}\) is:</p> \[\mathbf{x} = \begin{bmatrix} x &amp; y &amp; z \end{bmatrix}^T\] <p>and \(\mathbf{M}\), \(\mathbf{n}\) are, respectively:</p> \[\mathbf{M} = \begin{bmatrix} a &amp; h &amp; g \\ h &amp; b &amp; f \\ g &amp; f &amp; c \end{bmatrix}, ~~ \mathbf{n} = \begin{bmatrix} p \\ q \\ r \end{bmatrix}.\] <p>A sphere of radius 1 centered at origin would be defined by:</p> \[\mathbf{M} = \begin{bmatrix} 1 &amp; 0 &amp; 0 \\ 0 &amp; 1 &amp; 0 \\ 0 &amp; 0 &amp; 1 \end{bmatrix}, ~~ \mathbf{n} = \begin{bmatrix} 0 \\ 0 \\ 0 \end{bmatrix} ~ \text{and} ~ d=-1.\] <p>Eq. \(\eqref{eq_quad_gen}\) can also be written purely in matrix form as:</p> \[\mathit{S}: \mathbf{x}^T \mathbf{Q} \mathbf{x} = 0\] <p>where \(\mathbf{x}\) and \(\mathbf{Q}\) are, respectively:</p> \[\mathbf{x} = \begin{bmatrix} x &amp; y &amp; z &amp; 1 \end{bmatrix}^T, ~~ \mathbf{Q} = \left[ \begin{array}{c|c} \mathbf{M} &amp; \mathbf{n}\\ \hline \mathbf{n}^T &amp; d \end{array} \right].\] <p>The type of quadric is determined by some properties of the matrices \(\mathbf{M}\) and \(\mathbf{Q}\) as detailed <a href="http://www.geom.uiuc.edu/docs/reference/CRC-formulas/node61.html">here</a>.</p> <h1 id="measuring-with-a-magnetometer">Measuring with a magnetometer</h1> <p>Initially, let’s assume that we are in a magnetic disturbance free environment and that we have an ideal 3-axis magnetometer. Under these conditions, a magnetometer reading \(\mathbf{h}\) taken with an arbitrary orientation will be given by:</p> \[\mathbf{h} = \mathbf{R}_x(\phi) \mathbf{R}_y(\theta) \mathbf{R}_z(\varphi) \mathbf{h}_0, \label{eq_h} \tag{4}\] <p>where \(\mathbf{h}_0\) is the local Earth magnetic field given in \(\eqref{eq_h0}\) and \(\mathbf{R}_x(\phi)\), \(\mathbf{R}_y(\theta)\), \(\mathbf{R}_z(\varphi)\) are <a href="https://www.youtube.com/watch?v=wArGifkRD2A&amp;index=3&amp;list=PL_w_qWAQZtAZhtzPI5pkAtcUVgmzdAP8g">rotation matrices</a> around the frame of reference \(x\), \(y\), \(z\) axis, respectively. Complementary sources of information would be needed to determine the orientation. It is easy to see that samples locus is a sphere of radius \(\mathcal{F}\):</p> \[\mathbf{h}^T \mathbf{h} = \ldots = \mathcal{F}^2. \label{eq_h_const} \tag{5}\] <p>This way way of looking at the magnetometer samples gives us a geometric perspective of the problem. A calculated example is shown in the interactive plot found below.</p> <figure class="text-center"> <iframe src="https://teslabs.com/content/posts/magnetometer-calibration/example-1.html" width="90%" height="400" marginwidth="0" marginheight="0" scrolling="no"></iframe> <figcaption>Expected samples of a 3-axis magnetometer around Irvine, CA by the date of writing</figcaption> </figure> <h2 id="distortion-sources">Distortion sources</h2> <p>In real life nothing is ideal neither the environment is disturbance free. As detailed in this <a href="http://www.hindawi.com/journals/js/2010/967245/">publication</a>, there are two main categories of measurement distortion sources: instrumentation errors and magnetic interferences. In the next sections they will be briefly described, concluding with the measurement model.</p> <h3 id="instrumentation-errors">Instrumentation errors</h3> <div class="grid"> <div class="grid-col-67"> Instrumentation errors are unique and constant for each device. They can be modeled as a result of three components. Firstly, a scale factor, which can be modeled as a diagonal matrix: $$ \mathbf{S} = \begin{bmatrix} s_x &amp; 0 &amp; 0 \\ 0 &amp; s_y &amp; 0 \\ 0 &amp; 0 &amp; s_z \end{bmatrix} \label{eq_s} \tag{6} $$ Secondly, the non-orthogonality of the sensor axis, which can be modeled as: $$ \mathbf{N} = \begin{bmatrix} \mathbf{n}_x &amp; \mathbf{n}_y &amp; \mathbf{n}_z \end{bmatrix} \tag{7} $$ where each column represents a vector of size 3 corresponding to each sensor axis with respect to the sensor frame. Finally, a sensor offset which can be simply modeled as: $$ \mathbf{b}_{so} = \left[b_{so_x} ~ b_{so_y} ~ b_{so_z} \right]^T. \tag{8} $$ </div> <div class="grid-col-33"> <figure class="text-center"> <img src="/content/posts/magnetometer-calibration/inst_errors.svg" alt="Magnetometer inst. errors" /> <figcaption>Schematic representation of each instrumentation error (<span style="color: red;">-</span>)</figcaption> </figure> </div> </div> <h3 id="magnetic-interferences">Magnetic interferences</h3> <div class="grid"> <div class="grid-col-67"> <p> The magnetic interferences are caused by ferromagnetic elements present in the surroundings of the sensor. It is composed by permanent (hard iron) and induced magnetism (soft iron). Note that we ignore any non-constant magnetic interference. </p> Hard iron results from permanent magnets and magnetic hysteresis (remanence of magnetized iron). It is equivalent to a bias, which can be modeled as: $$ \mathbf{b}_{hi} = \left[b_{hi_x} ~ b_{hi_y} ~ b_{hi_z} \right]^T. \tag{9} $$ Soft iron is due by the interaction of an external magnetic field with ferromagnetic materials, causing a change in the intensity and direction of the sensed field. It can be modeled as: $$ \mathbf{A}_{si} = \begin{bmatrix} a_{11} &amp; a_{12} &amp; a_{13} \\ a_{12} &amp; a_{22} &amp; a_{23} \\ a_{31} &amp; a_{32} &amp; a_{33} \end{bmatrix} \label{eq_si} \tag{10} $$ <p> You may find useful to read this <a href="http://cache.freescale.com/files/sensors/doc/app_note/AN4247.pdf">application note</a>, where it is discussed which elements on a PCB can cause magnetic interference. </p> </div> <div class="grid-col-33"> <figure class="text-center"> <img src="/content/posts/magnetometer-calibration/mag_intf.svg" alt="Magnetic interferences" /> <figcaption>Schematic representation of each magnetic interference (<span style="color: red;">-</span>)</figcaption> </figure> </div> </div> <h2 id="measurement-model">Measurement model</h2> <p>After detailing each distortion source, we can easily write down the measurement model. All the distortion sources described in \(\eqref{eq_s}-\eqref{eq_si}\) can be combined to express the measured magnetic field \(\mathbf{h}_m\) as:</p> \[\mathbf{h}_m = \mathbf{SN}(\mathbf{A}_{si}\mathbf{h} + \mathbf{b}_{hi}) + \mathbf{b}_{so} \label{eq_h_meas_ns} \tag{11}\] <p>where \(\mathbf{h}\) is the true magnetic field given by \(\eqref{eq_h}\). It is worth to note at this point that our measurement model does not include any stochastic noise. This is in some way unrealistic, however, it simplifies the solution approach at the cost of a slightly biased solution (assuming a small amplitude noise). You may use the method described in this <a href="http://ieeexplore.ieee.org/xpls/abs_all.jsp?arnumber=6289882&amp;tag=1">paper</a> to extend our solution by including noise.</p> <p>Grouping terms \(\eqref{eq_h_meas_ns}\) can be re-written as:</p> \[\mathbf{h}_m = \mathbf{A}\mathbf{h} + \mathbf{b} \label{eq_h_meas} \tag{12}\] <p>where:</p> \[\begin{matrix} \mathbf{A} = \mathbf{SNA}_{si} \\ \mathbf{b} = \mathbf{SNb}_{hi} + \mathbf{b}_{so}. \end{matrix}\] <p>\(\mathbf{A}\) is a matrix combining scale factors, misalignments and soft-iron effects, while \(\mathbf{b}\) is the combined bias vector. It can be proved that the linear transformation of \(\mathbf{h}\) in \(\eqref{eq_h_meas}\) makes the measurements \(\mathbf{h}_m\) to lie on an ellipsoid. Therefore, we will see in the next section that the calibration process reduces to an ellipsoid fitting problem.</p> <h1 id="calibration">Calibration</h1> <p>It becomes clear from \(\eqref{eq_h_meas}\) that we need to find an estimate of \(\mathbf{A}\) and \(\mathbf{b}\), which we will note as \(\mathbf{\hat{A}}\) and \(\mathbf{\hat{b}}\). Then, any new measurement \(\mathbf{h}_n\) can be simply calibrated as:</p> \[\mathbf{\hat{h}}_n = \mathbf{\hat{A}}^{-1}(\mathbf{h}_n - \mathbf{\hat{b}}).\] <p>We first start expressing \(\mathbf{h}\), using \(\eqref{eq_h_meas}\), as:</p> \[\mathbf{h} = \mathbf{A}^{-1}(\mathbf{h}_m - \mathbf{b})\] <p>which combined with \(\eqref{eq_h_const}\) leads to:</p> \[\begin{align} &amp; \mathbf{h}_m^T \mathbf{A}^{-T} \mathbf{A}^{-1} \mathbf{h}_m - \\ &amp; 2 \mathbf{h}_m^T \mathbf{A}^{-T} \mathbf{A}^{-1} \mathbf{b} + \\ &amp; \mathbf{b}^T \mathbf{A}^{-T} \mathbf{A}^{-1} \mathbf{b} - \mathcal{F}^2 = 0. \end{align} \label{eq_cal_quad_ns} \tag{13}\] <p>Eq. \(\eqref{eq_cal_quad_ns}\) can be re-written in the familiar quadric form:</p> \[\mathbf{h}_m^T \mathbf{M} \mathbf{h}_m + \mathbf{h}_m^T \mathbf{n} + d = 0 \label{eq_cal_quad} \tag{14}\] <p>where:</p> \[\mathbf{M} = \mathbf{A}^{-T} \mathbf{A}^{-1} \label{eq_cal_M} \tag{15a}\] \[\mathbf{n} = -2 \mathbf{A}^{-T} \mathbf{A}^{-1} \mathbf{b} \label{eq_cal_n} \tag{15b}\] \[d = \mathbf{b}^T \mathbf{A}^{-T} \mathbf{A}^{-1} \mathbf{b} - \mathcal{F}^2. \label{eq_cal_d} \tag{15c}\] <p>As we noted in the previous section our quadric surface will be an ellipsoid. Thus, we need to use an ellipsoid fitting algorithm which provides us with estimates of the quadric surface parameters, \(\mathbf{\hat{M}}\), \(\mathbf{\hat{n}}\) and \(\hat{d}\). Multiple algorithms have been developed to fit a set of points to an ellipsoid surface. We will use the LS fitting method described in this <a href="http://ieeexplore.ieee.org/xpls/abs_all.jsp?arnumber=1290055">paper</a> by Q. Li et al. This method is easy to implement and there is also a MATLAB version readily <a href="http://www.mathworks.com/matlabcentral/fileexchange/23377-ellipsoid-fitting">available</a>. Even though presented as an iterative method, a single step fit is enough if the points describe well the ellipsoid (which will be our case). Furthermore, it is shown that it performs reasonably well for low noise levels.</p> <p>Using \(\eqref{eq_cal_quad}\) and \(\eqref{eq_cal_M}\)-\(\eqref{eq_cal_d}\) with the estimated values \(\mathbf{\hat{M}}\), \(\mathbf{\hat{n}}\) and \(\hat{d}\) we can find \(\mathbf{\hat{A}}\) (or \(\mathbf{\hat{A}}^{-1}\)) and \(\mathbf{\hat{b}}\). As detailed in this <a href="http://ieeexplore.ieee.org/xpls/abs_all.jsp?arnumber=6289882&amp;tag=1">paper</a>, you will need to find the proper scale of the solution, which is found to be:</p> \[\mathbf{\hat{b}} = - \mathbf{\hat{M}}^{-1} \mathbf{\hat{n}} \label{eq_cal_b} \tag{16}\] \[\mathbf{\hat{A}}^{-1} = \frac{\mathcal{F}} {\sqrt{\mathbf{\hat{n}}^T\mathbf{\hat{M}}^{-1}\mathbf{\hat{n}} - \hat{d}}} \mathbf{\hat{M}}^{\frac{1}{2}}. \label{eq_cal_A_1} \tag{17}\] <p>As you can observe from \(\eqref{eq_cal_A_1}\), knowledge of \(\mathcal{F}\) is required in order to calculate \(\mathbf{\hat{A}}^{-1}\). This will make the calibrated sphere to have radius \(\mathcal{F}\). However, in many applications such as in attitude estimation, the magnitude is irrelevant. If that is your case, you can make it one or any other convenient value.</p> <p>In order to conclude this section, the proposed code to perform the calibration is shown below. It uses <a href="http://www.numpy.org/">Numpy</a> and <a href="http://www.scipy.org/">SciPy</a> for numeric calculations as well as the sensor class detailed in the next section.</p> <figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">sys</span> <span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="n">np</span> <span class="kn">from</span> <span class="nn">scipy</span> <span class="kn">import</span> <span class="n">linalg</span> <span class="k">class</span> <span class="nc">Magnetometer</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span> <span class="s">''' Magnetometer class with calibration capabilities. Parameters ---------- sensor : str Sensor to use. bus : int Bus where the sensor is attached. F : float (optional) Expected earth magnetic field intensity, default=1. '''</span> <span class="c1"># available sensors </span> <span class="n">_sensors</span> <span class="o">=</span> <span class="p">{</span><span class="s">'ak8975c'</span><span class="p">:</span> <span class="n">SensorAK8975C</span><span class="p">}</span> <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">sensor</span><span class="p">,</span> <span class="n">bus</span><span class="p">,</span> <span class="n">F</span><span class="o">=</span><span class="mf">1.</span><span class="p">):</span> <span class="c1"># sensor </span> <span class="k">if</span> <span class="n">sensor</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">_sensors</span><span class="p">:</span> <span class="k">raise</span> <span class="nb">NotImplementedError</span><span class="p">(</span><span class="s">'Sensor %s not available'</span> <span class="o">%</span> <span class="n">sensor</span><span class="p">)</span> <span class="bp">self</span><span class="p">.</span><span class="n">sensor</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">_sensors</span><span class="p">[</span><span class="n">sensor</span><span class="p">](</span><span class="n">bus</span><span class="p">)</span> <span class="c1"># initialize values </span> <span class="bp">self</span><span class="p">.</span><span class="n">F</span> <span class="o">=</span> <span class="n">F</span> <span class="bp">self</span><span class="p">.</span><span class="n">b</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">zeros</span><span class="p">([</span><span class="mi">3</span><span class="p">,</span> <span class="mi">1</span><span class="p">])</span> <span class="bp">self</span><span class="p">.</span><span class="n">A_1</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">eye</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span> <span class="k">def</span> <span class="nf">read</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="s">''' Get a sample. Returns ------- s : list The sample in uT, [x,y,z] (corrected if performed calibration). '''</span> <span class="n">s</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">array</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">sensor</span><span class="p">.</span><span class="n">read</span><span class="p">()).</span><span class="n">reshape</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span> <span class="n">s</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">dot</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">A_1</span><span class="p">,</span> <span class="n">s</span> <span class="o">-</span> <span class="bp">self</span><span class="p">.</span><span class="n">b</span><span class="p">)</span> <span class="k">return</span> <span class="p">[</span><span class="n">s</span><span class="p">[</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">],</span> <span class="n">s</span><span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="mi">0</span><span class="p">],</span> <span class="n">s</span><span class="p">[</span><span class="mi">2</span><span class="p">,</span><span class="mi">0</span><span class="p">]]</span> <span class="k">def</span> <span class="nf">calibrate</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="s">''' Performs calibration. '''</span> <span class="k">print</span><span class="p">(</span><span class="s">'Collecting samples (Ctrl-C to stop and perform calibration)'</span><span class="p">)</span> <span class="k">try</span><span class="p">:</span> <span class="n">s</span> <span class="o">=</span> <span class="p">[]</span> <span class="n">n</span> <span class="o">=</span> <span class="mi">0</span> <span class="k">while</span> <span class="bp">True</span><span class="p">:</span> <span class="n">s</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">sensor</span><span class="p">.</span><span class="n">read</span><span class="p">())</span> <span class="n">n</span> <span class="o">+=</span> <span class="mi">1</span> <span class="n">sys</span><span class="p">.</span><span class="n">stdout</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="s">'</span><span class="se">\r</span><span class="s">Total: %d'</span> <span class="o">%</span> <span class="n">n</span><span class="p">)</span> <span class="n">sys</span><span class="p">.</span><span class="n">stdout</span><span class="p">.</span><span class="n">flush</span><span class="p">()</span> <span class="k">except</span> <span class="nb">KeyboardInterrupt</span><span class="p">:</span> <span class="k">pass</span> <span class="c1"># ellipsoid fit </span> <span class="n">s</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">array</span><span class="p">(</span><span class="n">s</span><span class="p">).</span><span class="n">T</span> <span class="n">M</span><span class="p">,</span> <span class="n">n</span><span class="p">,</span> <span class="n">d</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">__ellipsoid_fit</span><span class="p">(</span><span class="n">s</span><span class="p">)</span> <span class="c1"># calibration parameters </span> <span class="c1"># note: some implementations of sqrtm return complex type, taking real </span> <span class="n">M_1</span> <span class="o">=</span> <span class="n">linalg</span><span class="p">.</span><span class="n">inv</span><span class="p">(</span><span class="n">M</span><span class="p">)</span> <span class="bp">self</span><span class="p">.</span><span class="n">b</span> <span class="o">=</span> <span class="o">-</span><span class="n">np</span><span class="p">.</span><span class="n">dot</span><span class="p">(</span><span class="n">M_1</span><span class="p">,</span> <span class="n">n</span><span class="p">)</span> <span class="bp">self</span><span class="p">.</span><span class="n">A_1</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">real</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">F</span> <span class="o">/</span> <span class="n">np</span><span class="p">.</span><span class="n">sqrt</span><span class="p">(</span><span class="n">np</span><span class="p">.</span><span class="n">dot</span><span class="p">(</span><span class="n">n</span><span class="p">.</span><span class="n">T</span><span class="p">,</span> <span class="n">np</span><span class="p">.</span><span class="n">dot</span><span class="p">(</span><span class="n">M_1</span><span class="p">,</span> <span class="n">n</span><span class="p">))</span> <span class="o">-</span> <span class="n">d</span><span class="p">)</span> <span class="o">*</span> <span class="n">linalg</span><span class="p">.</span><span class="n">sqrtm</span><span class="p">(</span><span class="n">M</span><span class="p">))</span> <span class="k">def</span> <span class="nf">__ellipsoid_fit</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">s</span><span class="p">):</span> <span class="s">''' Estimate ellipsoid parameters from a set of points. Parameters ---------- s : array_like The samples (M,N) where M=3 (x,y,z) and N=number of samples. Returns ------- M, n, d : array_like, array_like, float The ellipsoid parameters M, n, d. References ---------- .. [1] Qingde Li; Griffiths, J.G., "Least squares ellipsoid specific fitting," in Geometric Modeling and Processing, 2004. Proceedings, vol., no., pp.335-340, 2004 '''</span> <span class="c1"># D (samples) </span> <span class="n">D</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">array</span><span class="p">([</span><span class="n">s</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">**</span><span class="mf">2.</span><span class="p">,</span> <span class="n">s</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">**</span><span class="mf">2.</span><span class="p">,</span> <span class="n">s</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="o">**</span><span class="mf">2.</span><span class="p">,</span> <span class="mf">2.</span><span class="o">*</span><span class="n">s</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">*</span><span class="n">s</span><span class="p">[</span><span class="mi">2</span><span class="p">],</span> <span class="mf">2.</span><span class="o">*</span><span class="n">s</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">*</span><span class="n">s</span><span class="p">[</span><span class="mi">2</span><span class="p">],</span> <span class="mf">2.</span><span class="o">*</span><span class="n">s</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">*</span><span class="n">s</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="mf">2.</span><span class="o">*</span><span class="n">s</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="mf">2.</span><span class="o">*</span><span class="n">s</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="mf">2.</span><span class="o">*</span><span class="n">s</span><span class="p">[</span><span class="mi">2</span><span class="p">],</span> <span class="n">np</span><span class="p">.</span><span class="n">ones_like</span><span class="p">(</span><span class="n">s</span><span class="p">[</span><span class="mi">0</span><span class="p">])])</span> <span class="c1"># S, S_11, S_12, S_21, S_22 (eq. 11) </span> <span class="n">S</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">dot</span><span class="p">(</span><span class="n">D</span><span class="p">,</span> <span class="n">D</span><span class="p">.</span><span class="n">T</span><span class="p">)</span> <span class="n">S_11</span> <span class="o">=</span> <span class="n">S</span><span class="p">[:</span><span class="mi">6</span><span class="p">,:</span><span class="mi">6</span><span class="p">]</span> <span class="n">S_12</span> <span class="o">=</span> <span class="n">S</span><span class="p">[:</span><span class="mi">6</span><span class="p">,</span><span class="mi">6</span><span class="p">:]</span> <span class="n">S_21</span> <span class="o">=</span> <span class="n">S</span><span class="p">[</span><span class="mi">6</span><span class="p">:,:</span><span class="mi">6</span><span class="p">]</span> <span class="n">S_22</span> <span class="o">=</span> <span class="n">S</span><span class="p">[</span><span class="mi">6</span><span class="p">:,</span><span class="mi">6</span><span class="p">:]</span> <span class="c1"># C (Eq. 8, k=4) </span> <span class="n">C</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">array</span><span class="p">([[</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">],</span> <span class="p">[</span> <span class="mi">1</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">],</span> <span class="p">[</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">],</span> <span class="p">[</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="mi">4</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">],</span> <span class="p">[</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="mi">4</span><span class="p">,</span> <span class="mi">0</span><span class="p">],</span> <span class="p">[</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="mi">4</span><span class="p">]])</span> <span class="c1"># v_1 (eq. 15, solution) </span> <span class="n">E</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">dot</span><span class="p">(</span><span class="n">linalg</span><span class="p">.</span><span class="n">inv</span><span class="p">(</span><span class="n">C</span><span class="p">),</span> <span class="n">S_11</span> <span class="o">-</span> <span class="n">np</span><span class="p">.</span><span class="n">dot</span><span class="p">(</span><span class="n">S_12</span><span class="p">,</span> <span class="n">np</span><span class="p">.</span><span class="n">dot</span><span class="p">(</span><span class="n">linalg</span><span class="p">.</span><span class="n">inv</span><span class="p">(</span><span class="n">S_22</span><span class="p">),</span> <span class="n">S_21</span><span class="p">)))</span> <span class="n">E_w</span><span class="p">,</span> <span class="n">E_v</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">linalg</span><span class="p">.</span><span class="n">eig</span><span class="p">(</span><span class="n">E</span><span class="p">)</span> <span class="n">v_1</span> <span class="o">=</span> <span class="n">E_v</span><span class="p">[:,</span> <span class="n">np</span><span class="p">.</span><span class="n">argmax</span><span class="p">(</span><span class="n">E_w</span><span class="p">)]</span> <span class="k">if</span> <span class="n">v_1</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">:</span> <span class="n">v_1</span> <span class="o">=</span> <span class="o">-</span><span class="n">v_1</span> <span class="c1"># v_2 (eq. 13, solution) </span> <span class="n">v_2</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">dot</span><span class="p">(</span><span class="n">np</span><span class="p">.</span><span class="n">dot</span><span class="p">(</span><span class="o">-</span><span class="n">np</span><span class="p">.</span><span class="n">linalg</span><span class="p">.</span><span class="n">inv</span><span class="p">(</span><span class="n">S_22</span><span class="p">),</span> <span class="n">S_21</span><span class="p">),</span> <span class="n">v_1</span><span class="p">)</span> <span class="c1"># quadric-form parameters </span> <span class="n">M</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">array</span><span class="p">([[</span><span class="n">v_1</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">v_1</span><span class="p">[</span><span class="mi">3</span><span class="p">],</span> <span class="n">v_1</span><span class="p">[</span><span class="mi">4</span><span class="p">]],</span> <span class="p">[</span><span class="n">v_1</span><span class="p">[</span><span class="mi">3</span><span class="p">],</span> <span class="n">v_1</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">v_1</span><span class="p">[</span><span class="mi">5</span><span class="p">]],</span> <span class="p">[</span><span class="n">v_1</span><span class="p">[</span><span class="mi">4</span><span class="p">],</span> <span class="n">v_1</span><span class="p">[</span><span class="mi">5</span><span class="p">],</span> <span class="n">v_1</span><span class="p">[</span><span class="mi">2</span><span class="p">]]])</span> <span class="n">n</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">array</span><span class="p">([[</span><span class="n">v_2</span><span class="p">[</span><span class="mi">0</span><span class="p">]],</span> <span class="p">[</span><span class="n">v_2</span><span class="p">[</span><span class="mi">1</span><span class="p">]],</span> <span class="p">[</span><span class="n">v_2</span><span class="p">[</span><span class="mi">2</span><span class="p">]]])</span> <span class="n">d</span> <span class="o">=</span> <span class="n">v_2</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span> <span class="k">return</span> <span class="n">M</span><span class="p">,</span> <span class="n">n</span><span class="p">,</span> <span class="n">d</span></code></pre></figure> <h1 id="example">Example</h1> <p>To conclude this post we will use samples from a real magnetometer, which will allow us to see if the proposed solution behaves as expected. It is important to note that we will not evaluate the accuracy of our solution, neither other things such as how the number of samples or their spatial distribution affects the calibration. These are topics that would require further study, out of the scope of this post.</p> <p>The chosen magnetometer is the one found inside the <a href="https://www.sparkfun.com/products/11486">Invensense MPU-9150</a>: AK8975C. It is a module that also contains an accelerometer and a gyroscope, not required for our purposes. A Raspberry Pi 2 will be used as a host platform, as shown below. This will allow us to quickly code a proof of concept by just using a few lines of Python. Note that you will need to activate the I2C driver and install <code class="language-plaintext highlighter-rouge">python-smbus</code> on the Raspberry Pi (details <a href="https://learn.adafruit.com/adafruits-raspberry-pi-lesson-4-gpio-setup/configuring-i2c">here</a>).</p> <figure class="text-center"> <img src="/content/posts/magnetometer-calibration/example-setup.jpg" style="" alt="Example setup" /> <figcaption>The setup used for this example</figcaption> </figure> <p>The proposed code for the AK8975C drive is:</p> <figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">smbus</span> <span class="c1"># MPU9150 used registers </span><span class="n">MPU9150_ADDR</span> <span class="o">=</span> <span class="mh">0x68</span> <span class="n">MPU9150_PWR_MGMT_1</span> <span class="o">=</span> <span class="mh">0x6B</span> <span class="n">MPU9150_INT_PIN_CFG</span> <span class="o">=</span> <span class="mh">0x37</span> <span class="n">MPU9150_INT_PIN_CFG_I2C_BYPASS_EN</span> <span class="o">=</span> <span class="mh">0x02</span> <span class="c1"># AK8975C used registers </span><span class="n">AK8975C_ADDR</span> <span class="o">=</span> <span class="mh">0x0C</span> <span class="n">AK8975C_ST1</span> <span class="o">=</span> <span class="mh">0x02</span> <span class="n">AK8975C_ST1_DRDY</span> <span class="o">=</span> <span class="mh">0x01</span> <span class="n">AK8975C_HXL</span> <span class="o">=</span> <span class="mh">0x03</span> <span class="n">AK8975C_CNTL</span> <span class="o">=</span> <span class="mh">0x0A</span> <span class="n">AK8975C_CNTL_SINGLE</span> <span class="o">=</span> <span class="mh">0x01</span> <span class="c1"># convert to s16 from two's complement </span><span class="n">to_s16</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">n</span><span class="p">:</span> <span class="n">n</span> <span class="o">-</span> <span class="mh">0x10000</span> <span class="k">if</span> <span class="n">n</span> <span class="o">&gt;</span> <span class="mh">0x7FFF</span> <span class="k">else</span> <span class="n">n</span> <span class="k">class</span> <span class="nc">SensorAK8975C</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span> <span class="s">''' AK8975C Simple Driver. Parameters ---------- bus : int I2C bus number (e.g. 1). '''</span> <span class="c1"># sensor sensitivity (uT/LSB) </span> <span class="n">_sensitivity</span> <span class="o">=</span> <span class="mf">0.3</span> <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">bus</span><span class="p">):</span> <span class="bp">self</span><span class="p">.</span><span class="n">bus</span> <span class="o">=</span> <span class="n">smbus</span><span class="p">.</span><span class="n">SMBus</span><span class="p">(</span><span class="n">bus</span><span class="p">)</span> <span class="c1"># wake up and set bypass for AK8975C </span> <span class="bp">self</span><span class="p">.</span><span class="n">bus</span><span class="p">.</span><span class="n">write_byte_data</span><span class="p">(</span><span class="n">MPU9150_ADDR</span><span class="p">,</span> <span class="n">MPU9150_PWR_MGMT_1</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="bp">self</span><span class="p">.</span><span class="n">bus</span><span class="p">.</span><span class="n">write_byte_data</span><span class="p">(</span><span class="n">MPU9150_ADDR</span><span class="p">,</span> <span class="n">MPU9150_INT_PIN_CFG</span><span class="p">,</span> <span class="n">MPU9150_INT_PIN_CFG_I2C_BYPASS_EN</span><span class="p">)</span> <span class="k">def</span> <span class="nf">__del__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="bp">self</span><span class="p">.</span><span class="n">bus</span><span class="p">.</span><span class="n">close</span><span class="p">()</span> <span class="k">def</span> <span class="nf">read</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="s">''' Get a sample. Returns ------- s : list The sample in uT, [x, y, z]. '''</span> <span class="c1"># request single shot </span> <span class="bp">self</span><span class="p">.</span><span class="n">bus</span><span class="p">.</span><span class="n">write_byte_data</span><span class="p">(</span><span class="n">AK8975C_ADDR</span><span class="p">,</span> <span class="n">AK8975C_CNTL</span><span class="p">,</span> <span class="n">AK8975C_CNTL_SINGLE</span><span class="p">)</span> <span class="c1"># wait for data ready </span> <span class="n">r</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">bus</span><span class="p">.</span><span class="n">read_byte_data</span><span class="p">(</span><span class="n">AK8975C_ADDR</span><span class="p">,</span> <span class="n">AK8975C_ST1</span><span class="p">)</span> <span class="k">while</span> <span class="ow">not</span> <span class="p">(</span><span class="n">r</span> <span class="o">&amp;</span> <span class="n">AK8975C_ST1_DRDY</span><span class="p">):</span> <span class="n">r</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">bus</span><span class="p">.</span><span class="n">read_byte_data</span><span class="p">(</span><span class="n">AK8975C_ADDR</span><span class="p">,</span> <span class="n">AK8975C_ST1</span><span class="p">)</span> <span class="c1"># read x, y, z </span> <span class="n">data</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">bus</span><span class="p">.</span><span class="n">read_i2c_block_data</span><span class="p">(</span><span class="n">AK8975C_ADDR</span><span class="p">,</span> <span class="n">AK8975C_HXL</span><span class="p">,</span> <span class="mi">6</span><span class="p">)</span> <span class="k">return</span> <span class="p">[</span><span class="bp">self</span><span class="p">.</span><span class="n">_sensitivity</span> <span class="o">*</span> <span class="n">to_s16</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">|</span> <span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">&lt;&lt;</span> <span class="mi">8</span><span class="p">)),</span> <span class="bp">self</span><span class="p">.</span><span class="n">_sensitivity</span> <span class="o">*</span> <span class="n">to_s16</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">|</span> <span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span> <span class="o">&lt;&lt;</span> <span class="mi">8</span><span class="p">)),</span> <span class="bp">self</span><span class="p">.</span><span class="n">_sensitivity</span> <span class="o">*</span> <span class="n">to_s16</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span> <span class="o">|</span> <span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="mi">5</span><span class="p">]</span> <span class="o">&lt;&lt;</span> <span class="mi">8</span><span class="p">))]</span></code></pre></figure> <p>Merging the sensor driver shown above with the code in the previous section will allow us to both sample and calibrate. We can also append the code shown below, which will allow us to store non-calibrated and calibrated samples to then visualize the results with any plotting library:</p> <figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="kn">from</span> <span class="nn">time</span> <span class="kn">import</span> <span class="n">sleep</span> <span class="k">def</span> <span class="nf">collect</span><span class="p">(</span><span class="n">fn</span><span class="p">,</span> <span class="n">fs</span><span class="o">=</span><span class="mi">10</span><span class="p">):</span> <span class="s">''' Collect magnetometer samples Parameters ---------- fn : str Output file. fs : int (optional) Approximate sampling frequency (Hz), default 10 Hz. '''</span> <span class="k">print</span><span class="p">(</span><span class="s">'Collecting [%s]. Ctrl-C to finish'</span> <span class="o">%</span> <span class="n">fn</span><span class="p">)</span> <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">fn</span><span class="p">,</span> <span class="s">'w'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span> <span class="n">f</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="s">'x,y,z</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span> <span class="k">try</span><span class="p">:</span> <span class="k">while</span> <span class="bp">True</span><span class="p">:</span> <span class="n">s</span> <span class="o">=</span> <span class="n">m</span><span class="p">.</span><span class="n">read</span><span class="p">()</span> <span class="n">f</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="s">'%.1f,%.1f,%.1f</span><span class="se">\n</span><span class="s">'</span> <span class="o">%</span> <span class="p">(</span><span class="n">s</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">s</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">s</span><span class="p">[</span><span class="mi">2</span><span class="p">]))</span> <span class="n">sleep</span><span class="p">(</span><span class="mf">1.</span><span class="o">/</span><span class="n">fs</span><span class="p">)</span> <span class="k">except</span> <span class="nb">KeyboardInterrupt</span><span class="p">:</span> <span class="k">pass</span> <span class="n">m</span> <span class="o">=</span> <span class="n">Magnetometer</span><span class="p">(</span><span class="s">'ak8975c'</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mf">46.85</span><span class="p">)</span> <span class="n">collect</span><span class="p">(</span><span class="s">'ncal.csv'</span><span class="p">)</span> <span class="n">m</span><span class="p">.</span><span class="n">calibrate</span><span class="p">()</span> <span class="n">collect</span><span class="p">(</span><span class="s">'cal.csv'</span><span class="p">)</span></code></pre></figure> <p>Real results are displayed in the following interactive plot:</p> <figure class="text-center"> <iframe src="https://teslabs.com/content/posts/magnetometer-calibration/example-2.html" width="90%" height="400" marginwidth="0" marginheight="0" scrolling="no"></iframe> <figcaption>Samples taken <span style="color:red;">before</span> and <span style="color:blue;">after</span> calibration shown together with the reference sphere.</figcaption> </figure> Sun, 06 Dec 2015 00:00:00 +0000 https://teslabs.com/articles/magnetometer-calibration/ https://teslabs.com/articles/magnetometer-calibration/ TopoJSON maps for Catalonia <p>After trying some of the <a href="//github.com/mbostock/topojson">TopoJSON</a> <a href="//github.com/mbostock/topojson/wiki/Gallery">examples</a> I could not resist the temptation of learning more about it. As a Catalan citizen, I decided to focus on <a href="//en.wikipedia.org/wiki/Catalonia">Catalonia</a> (or <em>Catalunya</em> in Catalan). Before starting I did a quick search to see if somebody else had already used TopoJSON in my region. I found some nice examples from a couple of authors: <a href="//bl.ocks.org/martgnz">martgnz</a> and <a href="//bl.ocks.org/rveciana">rveciana</a>. However in these examples little information is given on how maps are generated. Particularly topics like map simplification or projection are not discussed. So I decided to write this post where I explain in some detail the process of generating the TopoJSON maps for Catalonia.</p> <blockquote> <p>Together with this post I have created the <a href="//github.com/teslabs/cat-topojson">cat-topojson</a> utility which will allow you to easily generate the maps.</p> </blockquote> <script src="//d3js.org/d3.v3.min.js" charset="utf-8"></script> <script src="//d3js.org/topojson.v1.min.js"></script> <style> .land { fill: white; transition: fill 0.4s ease; } .land:hover { fill: #57ad68; } .border-in { fill: none; stroke: black; stroke-linejoin: round; stroke-linecap: round; } .border-out { fill: none; stroke: black; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; } </style> <script> var width = 500, height = 500; var path = d3.geo.path() .projection(null); d3.json("https://teslabs.com/content/posts/topojson-catalonia/cat-comarques.json", function(error, cat) { if (error) throw error; var svg = d3.select("div#map-example").append("svg") .attr("width", width) .attr("height", height); svg.append("g") .selectAll("path") .data(topojson.feature(cat, cat.objects.comarques).features) .enter().append("path") .attr("d", path) .attr("class", "land") .append("title") .text(function(d) { return d.properties.nom + ": " + d.properties.sup + " km²"; }); svg.append("path") .datum(topojson.mesh(cat, cat.objects.comarques, function (a, b) { return a !== b; })) .attr("class", "border-in") .attr("d", path); svg.append("path") .datum(topojson.mesh(cat, cat.objects.comarques, function (a, b) { return a === b; })) .attr("class", "border-out") .attr("d", path); }); </script> <figure> <div id="map-example" class="text-center"></div> <figcaption> Example: hover the mouse on any county to know its area! </figcaption> </figure> <h1 id="required-tools">Required tools</h1> <p>The first requirement is the TopoJSON host tool, <code class="language-plaintext highlighter-rouge">topojson</code>, which in turn requires <a href="//nodejs.org/en/">Node.js</a>. If you are on OS X, and assuming you use <a href="//brew.sh/">brew</a>, you can easily get both by typing:</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>brew <span class="nb">install </span>node <span class="nv">$ </span>npm <span class="nb">install</span> <span class="nt">-g</span> topojson</code></pre></figure> <p>In this post we will also use some tools part of <a href="//www.gdal.org/">GDAL</a>, useful to convert from one coordinate system to another or simply to retrieve information from map files. It can also be installed using <code class="language-plaintext highlighter-rouge">brew</code>:</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>brew <span class="nb">install </span>gdal</code></pre></figure> <h1 id="source-maps">Source maps</h1> <p>Before we proceed further, we need to obtain the source maps. They are freely available from <a href="//www.icc.cat/vissir3">ICGC</a> (registration is required). The maps of interest are called <em>Base Municipal</em> and are offered in multiple scales (1:50, 1:250 and 1:1000) and formats (DXF, DGN and SHP). In the examples found in this post we will use the scale 1:50 and the format SHP (shapefile), which is the only one of the list supported by <code class="language-plaintext highlighter-rouge">topojson</code>. The specification for these maps can be found <a href="//www.icc.es/cat/Home-ICC/Digital-geoinformation/About-ICGC-geoinformation/Technical-specifications">here</a>. A summary of the relevant information is given below.</p> <p>The shapefiles of interest contained in the downloaded file are the following:</p> <table> <thead> <tr> <th>File name</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td><code>bm[eeee]mv33sh1fpm[m]_[aaaammdd]_[c].shp</code></td> <td>Municipality Polygons</td> </tr> <tr> <td><code>bm[eeee]mv33sh1fpc[m]_[aaaammdd]_[c].shp</code></td> <td>County Polygons</td> </tr> <tr> <td><code>bm[eeee]mv33sh1fpp[m]_[aaaammdd]_[c].shp</code></td> <td>Province Polygons</td> </tr> </tbody> </table> <p>where <code class="language-plaintext highlighter-rouge">eeee</code> is the scale (i.e., <code class="language-plaintext highlighter-rouge">50</code>, <code class="language-plaintext highlighter-rouge">250</code>, <code class="language-plaintext highlighter-rouge">1000</code>), <code class="language-plaintext highlighter-rouge">m</code> is the frame coordinate reference (only <code class="language-plaintext highlighter-rouge">1</code> is available, meaning EPSG:25831 - ETRS89 / UTM zone 31N), <code class="language-plaintext highlighter-rouge">aaaammdd</code> is the maps reference date (e.g. <code class="language-plaintext highlighter-rouge">20150501</code>) and <code class="language-plaintext highlighter-rouge">c</code> is the distribution revision (e.g. <code class="language-plaintext highlighter-rouge">0</code>). Other files are provided, e.g. the location of municipality capital cities, but they will not be analyzed in this post.</p> <p>We can easily get detailed information of each file using the <code class="language-plaintext highlighter-rouge">ogrinfo</code> utility provided by GDAL, e.g.</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>ogrinfo <span class="nt">-al</span> bm50mv33sh1fpm1_20150501_0.shp | less Geometry: Polygon Feature Count: 947 Extent: <span class="o">(</span>260188.973378, 4488778.587563<span class="o">)</span> - <span class="o">(</span>527401.970714, 4747980.912187<span class="o">)</span> Layer SRS WKT: PROJCS[<span class="s2">"ETRS_1989_UTM_Zone_31N"</span>, GEOGCS[<span class="s2">"GCS_ETRS_1989"</span>, DATUM[<span class="s2">"European_Terrestrial_Reference_System_1989"</span>, SPHEROID[<span class="s2">"GRS_1980"</span>,6378137.0,298.257222101]], PRIMEM[<span class="s2">"Greenwich"</span>,0.0], UNIT[<span class="s2">"Degree"</span>,0.0174532925199433]], PROJECTION[<span class="s2">"Transverse_Mercator"</span><span class="o">]</span>, PARAMETER[<span class="s2">"False_Easting"</span>,500000.0], PARAMETER[<span class="s2">"False_Northing"</span>,0.0], PARAMETER[<span class="s2">"Central_Meridian"</span>,3.0], PARAMETER[<span class="s2">"Scale_Factor"</span>,0.9996], PARAMETER[<span class="s2">"Latitude_Of_Origin"</span>,0.0], UNIT[<span class="s2">"Meter"</span>,1.0]] MUNICIPI: String <span class="o">(</span>6.0<span class="o">)</span> COMARCA: String <span class="o">(</span>2.0<span class="o">)</span> PROVINCIA: String <span class="o">(</span>2.0<span class="o">)</span> NOM_MUNI: String <span class="o">(</span>45.0<span class="o">)</span> NOMN_MUNI: String <span class="o">(</span>45.0<span class="o">)</span> NOMG_MUNI: String <span class="o">(</span>45.0<span class="o">)</span> CAP_MUNI: String <span class="o">(</span>30.0<span class="o">)</span> CAPN_MUNI: String <span class="o">(</span>30.0<span class="o">)</span> CAPG_MUNI: String <span class="o">(</span>30.0<span class="o">)</span> SUP_MUNI: Real <span class="o">(</span>6.2<span class="o">)</span> ORSUP_MUNI: String <span class="o">(</span>1.0<span class="o">)</span> ...</code></pre></figure> <h1 id="generation-of-the-topojson-maps">Generation of the TopoJSON maps</h1> <p>In the following sections the process of generating the TopoJSON maps from the ICGC shapefiles is discussed.</p> <h2 id="geometry">Geometry</h2> <p>There are <a href="//resources.arcgis.com/en/help/main/10.1/index.html#//00v20000000q000000">two types</a> of coordinate systems used in maps: <a href="//resources.arcgis.com/en/help/main/10.1/index.html#/What_are_projected_coordinate_systems/003r0000000p000000/">projected</a> (X, Y) or <a href="//resources.arcgis.com/en/help/main/10.1/index.html#/What_are_geographic_coordinate_systems/003r00000006000000/">geographic</a> (\(\lambda\), \(\varphi\)). As we have just seen in the previous section, the maps provided by ICGC are projected in <a href="//resources.arcgis.com/en/help/main/10.1/index.html#/Universal_Transverse_Mercator/003r00000049000000/">UTM</a>. Advantages of using projected coordinates include better simplification results as well as reduced overhead on the rendering process as stated in this <a href="//bl.ocks.org/mbostock/5557726">example</a>. You can use either one or the other with D3.js, so choose what best fits your application. In this post examples for both cases are provided.</p> <p>In order to change from one coordinate system to another, you can use the <code class="language-plaintext highlighter-rouge">ogr2ogr</code> utility provided by GDAL. Actually, <code class="language-plaintext highlighter-rouge">ogr2ogr</code> offers many more possibilities (e.g. filtering), but it is left to the reader to explore them. As a quick example, if you want to convert the counties shapefile to the <a href="//en.wikipedia.org/wiki/World_Geodetic_System">World Geodetic System</a>, <a href="//spatialreference.org/ref/epsg/4326/">EPSG:4326</a>:</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>ogr2ogr <span class="se">\</span> <span class="nt">-f</span> <span class="s1">'ESRI Shapefile'</span> <span class="se">\</span> <span class="nt">-t_srs</span> EPSG:4326 <span class="se">\</span> counties-wgs.shp <span class="se">\</span> bm50mv33sh1fpc1_20150501_0.shp</code></pre></figure> <p>Provided that your input maps are in geographical coordinates, you could also use the <code class="language-plaintext highlighter-rouge">topojson --projection</code> option to apply one of the <a href="//github.com/mbostock/d3/wiki/Geo-Projections">D3.js projections</a>.</p> <p>Finally, when using projected coordinates, we can specify <code class="language-plaintext highlighter-rouge">--width</code>, <code class="language-plaintext highlighter-rouge">--height</code> and <code class="language-plaintext highlighter-rouge">--margin</code> to fit a viewport of the specified size. This will simplify the map usage if we know in advance where we are going to display the map, as we will not need to translate or scale it (see the examples).</p> <h2 id="simplification-and-quantization">Simplification and quantization</h2> <p>An important thing to care about when distributing maps is its complexity and size, both intrinsically related. Two techniques can be used to reduce both: geometry simplification and quantization.</p> <p>As illustrated, in this <a href="//bost.ocks.org/mike/simplify/">article</a> geometry simplification reduces the number of points given an area threshold. This area threshold is specified through the <code class="language-plaintext highlighter-rouge">-s</code> argument. It is given in <a href="//en.wikipedia.org/wiki/Steradian">steradians</a> in geographical coordinates. In case of projected coordinates it is given as squared base units, e.g. if we use <code class="language-plaintext highlighter-rouge">--width</code> and/or <code class="language-plaintext highlighter-rouge">--height</code> it will be in px<sup>2</sup>.</p> <p>Quantization is about numeric precision. TopoJSON uses <a href="//www.digitalsignallabs.com/fp.pdf">fixed point</a> coordinates, and allows you to specify the number of differentiable values. It is controlled by the <code class="language-plaintext highlighter-rouge">-q</code> parameter, or <code class="language-plaintext highlighter-rouge">--pre-quantization</code> and <code class="language-plaintext highlighter-rouge">--post-quantization</code> to specify input and output quantizations, respectively. A value of 10.000 differentiable values is used by default, which in most cases will be fine. Roughly speaking, the more values, the larger the size of the map.</p> <p>In any case, the simplest way to choose the right simplification and quantization parameters is to try and see how map looks. Three illustrative examples are shown below, exaggerating the effects of simplification and quantization in the second and third maps, respectively.</p> <div class="grid"> <div class="grid-col-33" id="map-simpl-1"></div> <div class="grid-col-33" id="map-simpl-2"></div> <div class="grid-col-33" id="map-quant"></div> </div> <style> .sq-land { fill: white; stroke: black; stroke-width: 0.5px; } </style> <script> d3.json("https://teslabs.com/content/posts/topojson-catalonia/cat-comarques-simpl-1.json", function(error, cat) { if (error) throw error; var path = d3.geo.path() .projection(null); var svg = d3.select("div#map-simpl-1").append("svg") .attr("width", 250) .attr("height", 250); svg.append("path") .datum(topojson.feature(cat, cat.objects.comarques)) .attr("class", "sq-land") .attr("d", path); }); d3.json("https://teslabs.com/content/posts/topojson-catalonia/cat-comarques-simpl-2.json", function(error, cat) { if (error) throw error; var path = d3.geo.path() .projection(null); var svg = d3.select("div#map-simpl-2").append("svg") .attr("width", 250) .attr("height", 250); svg.append("path") .datum(topojson.feature(cat, cat.objects.comarques)) .attr("class", "sq-land") .attr("d", path); }); d3.json("https://teslabs.com/content/posts/topojson-catalonia/cat-comarques-quant.json", function(error, cat) { if (error) throw error; var path = d3.geo.path() .projection(null); var svg = d3.select("div#map-quant").append("svg") .attr("width", 250) .attr("height", 250); svg.append("path") .datum(topojson.feature(cat, cat.objects.comarques)) .attr("class", "sq-land") .attr("d", path); }); </script> <h2 id="properties">Properties</h2> <p>The ICGC shapefiles contain some properties which are useful to retain (e.g. province names, area, etc.). They are detailed in the specification file.</p> <p>By default, <code class="language-plaintext highlighter-rouge">topojson</code> does not copy the properties contained in the input files. The <code class="language-plaintext highlighter-rouge">-p</code> argument can be used to copy all properties, or <code class="language-plaintext highlighter-rouge">-p target=source</code> to only keep the <code class="language-plaintext highlighter-rouge">source</code> property while renaming it to <code class="language-plaintext highlighter-rouge">target</code>. More than one property can be copied by passing multiple <code class="language-plaintext highlighter-rouge">-p</code> arguments or by appending multiple properties, e.g. <code class="language-plaintext highlighter-rouge">-p name=NAME,surname=SURN</code>. Furthermore, prepending a <code class="language-plaintext highlighter-rouge">+</code> sign to a property name will force the property to be a number, e.g. <code class="language-plaintext highlighter-rouge">-p area=+AREA</code>.</p> <p>Another important argument is <code class="language-plaintext highlighter-rouge">--id-property</code>. It is used to assign any of the properties to the ID of the geometry.</p> <h1 id="examples">Examples</h1> <p>Finally to conclude this post a couple of minimal examples are provided. Note that not many details are given on how to use D3.js/TopoJSON, as it is not the aim of this post. If you want to learn more about the topic, you can find great tutorials online like <a href="//bost.ocks.org/mike/map/">this</a>.</p> <p>In order to run the examples you will need an HTTP server, e.g.</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>python <span class="nt">-m</span> SimpleHTTPServer Serving HTTP on 0.0.0.0 port 8000 ...</code></pre></figure> <h2 id="projected-map">Projected map</h2> <p>In this example we will use the counties (<em>comarques</em>) map and fit it to a viewport of 500x500 px. As the source map is already projected, we can just feed it into <code class="language-plaintext highlighter-rouge">topojson</code>:</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>topojson <span class="se">\</span> <span class="nt">-o</span> cat-comarques.json <span class="se">\</span> <span class="nt">--width</span><span class="o">=</span>500 <span class="nt">--height</span><span class="o">=</span>500 <span class="se">\</span> <span class="nt">--simplify</span><span class="o">=</span>2 <span class="se">\</span> <span class="nt">--id-property</span><span class="o">=</span>+COMARCA <span class="se">\</span> <span class="nt">-p</span> <span class="nv">nom</span><span class="o">=</span>NOM_COMAR <span class="se">\</span> <span class="nt">-p</span> <span class="nv">cap</span><span class="o">=</span>CAP_COMAR <span class="se">\</span> <span class="nt">-p</span> <span class="nv">sup</span><span class="o">=</span>SUP_COMAR <span class="se">\</span> <span class="nt">--</span> <span class="nv">comarques</span><span class="o">=</span>bm50mv33sh1fpc1_20150501_0.shp</code></pre></figure> <p>Then, we can quickly visualize the results creating an html file with the following code:</p> <figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="cp">&lt;!DOCTYPE html&gt;</span> <span class="nt">&lt;meta</span> <span class="na">charset=</span><span class="s">"utf-8"</span><span class="nt">&gt;</span> <span class="nt">&lt;style&gt;</span> <span class="nc">.land</span> <span class="p">{</span> <span class="py">fill</span><span class="p">:</span> <span class="m">#ddc</span><span class="p">;</span> <span class="py">stroke</span><span class="p">:</span> <span class="no">white</span><span class="p">;</span> <span class="p">}</span> <span class="nt">&lt;/style&gt;</span> <span class="nt">&lt;body&gt;</span> <span class="nt">&lt;script </span><span class="na">src=</span><span class="s">"//d3js.org/d3.v3.min.js"</span> <span class="na">charset=</span><span class="s">"utf-8"</span><span class="nt">&gt;&lt;/script&gt;</span> <span class="nt">&lt;script </span><span class="na">src=</span><span class="s">"//d3js.org/topojson.v1.min.js"</span><span class="nt">&gt;&lt;/script&gt;</span> <span class="nt">&lt;script&gt;</span> <span class="kd">var</span> <span class="nx">width</span> <span class="o">=</span> <span class="mi">500</span><span class="p">,</span> <span class="nx">height</span> <span class="o">=</span> <span class="mi">500</span><span class="p">;</span> <span class="c1">// no need to project!</span> <span class="kd">var</span> <span class="nx">path</span> <span class="o">=</span> <span class="nx">d3</span><span class="p">.</span><span class="nx">geo</span><span class="p">.</span><span class="nx">path</span><span class="p">()</span> <span class="p">.</span><span class="nx">projection</span><span class="p">(</span><span class="kc">null</span><span class="p">);</span> <span class="kd">var</span> <span class="nx">svg</span> <span class="o">=</span> <span class="nx">d3</span><span class="p">.</span><span class="nx">select</span><span class="p">(</span><span class="dl">"</span><span class="s2">body</span><span class="dl">"</span><span class="p">).</span><span class="nx">append</span><span class="p">(</span><span class="dl">"</span><span class="s2">svg</span><span class="dl">"</span><span class="p">)</span> <span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="dl">"</span><span class="s2">width</span><span class="dl">"</span><span class="p">,</span> <span class="nx">width</span><span class="p">)</span> <span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="dl">"</span><span class="s2">height</span><span class="dl">"</span><span class="p">,</span> <span class="nx">height</span><span class="p">);</span> <span class="nx">d3</span><span class="p">.</span><span class="nx">json</span><span class="p">(</span><span class="dl">"</span><span class="s2">cat-comarques.json</span><span class="dl">"</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">error</span><span class="p">,</span> <span class="nx">cat</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="k">throw</span> <span class="nx">error</span><span class="p">;</span> <span class="nx">svg</span><span class="p">.</span><span class="nx">append</span><span class="p">(</span><span class="dl">"</span><span class="s2">path</span><span class="dl">"</span><span class="p">)</span> <span class="p">.</span><span class="nx">datum</span><span class="p">(</span><span class="nx">topojson</span><span class="p">.</span><span class="nx">feature</span><span class="p">(</span><span class="nx">cat</span><span class="p">,</span> <span class="nx">cat</span><span class="p">.</span><span class="nx">objects</span><span class="p">.</span><span class="nx">comarques</span><span class="p">))</span> <span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="dl">"</span><span class="s2">d</span><span class="dl">"</span><span class="p">,</span> <span class="nx">path</span><span class="p">)</span> <span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="dl">"</span><span class="s2">class</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">land</span><span class="dl">"</span><span class="p">)</span> <span class="p">});</span> <span class="nt">&lt;/script&gt;</span></code></pre></figure> <p>…and voilà:</p> <iframe src="https://teslabs.com/content/posts/topojson-catalonia/example-1.html" width="500" height="500" marginwidth="0" marginheight="0" scrolling="no"></iframe> <p><a href="https://teslabs.com/content/posts/topojson-catalonia/example-1.html" target="_blank">Open in a new window</a></p> <h2 id="non-projected-map">Non-projected map</h2> <p>In this second example we will use the municipalities (<em>municipis</em>) map but the projection will be done on the browser. We first use <code class="language-plaintext highlighter-rouge">ogr2ogr</code> to convert our map to the <a href="//spatialreference.org/ref/epsg/4326/">WGS-84</a> geographical coordinate system:</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>ogr2ogr <span class="se">\</span> <span class="nt">-f</span> <span class="s1">'ESRI Shapefile'</span> <span class="se">\</span> <span class="nt">-t_srs</span> EPSG:4326 <span class="se">\</span> municipis-wgs.shp <span class="se">\</span> bm50mv33sh1fpm1_20150501_0.shp</code></pre></figure> <p>Then, we use <code class="language-plaintext highlighter-rouge">topojson</code> to generate the map:</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>topojson <span class="se">\</span> <span class="nt">-o</span> cat-municipis.json <span class="se">\</span> <span class="nt">--simplify</span><span class="o">=</span>1e-8 <span class="se">\</span> <span class="nt">--id-property</span><span class="o">=</span>+MUNICIPI <span class="se">\</span> <span class="nt">-p</span> <span class="nv">nom</span><span class="o">=</span>NOM_MUNI <span class="se">\</span> <span class="nt">-p</span> <span class="nv">comarca</span><span class="o">=</span>+COMARCA <span class="se">\</span> <span class="nt">-p</span> <span class="nv">provincia</span><span class="o">=</span>+PROVINCIA <span class="se">\</span> <span class="nt">-p</span> <span class="nv">sup</span><span class="o">=</span>SUP_MUNI <span class="se">\</span> <span class="nt">--</span> <span class="nv">municipis</span><span class="o">=</span>municipis-wgs.shp</code></pre></figure> <p>Note that an area threshold of 10<sup>-8</sup> sr has proved to deliver a good compromise after a few tries.</p> <p>When using a projected map already adjusted to a viewport we do not need to care about centering or scaling it, but now we do. Finding the right values is a tedious task, as you need to go through a trial and error process until you find an acceptable result. However, there is an easy way to automatically center and scale a map described in this great <a href="//bl.ocks.org/mbostock/4707858">example</a>, which could even be used to calculate the right static values. Summarizing, the example code looks like this:</p> <figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="cp">&lt;!DOCTYPE html&gt;</span> <span class="nt">&lt;meta</span> <span class="na">charset=</span><span class="s">"utf-8"</span><span class="nt">&gt;</span> <span class="nt">&lt;style&gt;</span> <span class="nc">.land</span> <span class="p">{</span> <span class="py">fill</span><span class="p">:</span> <span class="m">#ddc</span><span class="p">;</span> <span class="py">stroke</span><span class="p">:</span> <span class="no">white</span><span class="p">;</span> <span class="p">}</span> <span class="nt">&lt;/style&gt;</span> <span class="nt">&lt;body&gt;</span> <span class="nt">&lt;script </span><span class="na">src=</span><span class="s">"//d3js.org/d3.v3.min.js"</span> <span class="na">charset=</span><span class="s">"utf-8"</span><span class="nt">&gt;&lt;/script&gt;</span> <span class="nt">&lt;script </span><span class="na">src=</span><span class="s">"//d3js.org/topojson.v1.min.js"</span><span class="nt">&gt;&lt;/script&gt;</span> <span class="nt">&lt;script&gt;</span> <span class="kd">var</span> <span class="nx">width</span> <span class="o">=</span> <span class="mi">500</span><span class="p">,</span> <span class="nx">height</span> <span class="o">=</span> <span class="mi">500</span><span class="p">;</span> <span class="kd">var</span> <span class="nx">projection</span> <span class="o">=</span> <span class="nx">d3</span><span class="p">.</span><span class="nx">geo</span><span class="p">.</span><span class="nx">mercator</span><span class="p">();</span> <span class="kd">var</span> <span class="nx">path</span> <span class="o">=</span> <span class="nx">d3</span><span class="p">.</span><span class="nx">geo</span><span class="p">.</span><span class="nx">path</span><span class="p">()</span> <span class="p">.</span><span class="nx">projection</span><span class="p">(</span><span class="nx">projection</span><span class="p">);</span> <span class="kd">var</span> <span class="nx">svg</span> <span class="o">=</span> <span class="nx">d3</span><span class="p">.</span><span class="nx">select</span><span class="p">(</span><span class="dl">"</span><span class="s2">body</span><span class="dl">"</span><span class="p">).</span><span class="nx">append</span><span class="p">(</span><span class="dl">"</span><span class="s2">svg</span><span class="dl">"</span><span class="p">)</span> <span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="dl">"</span><span class="s2">width</span><span class="dl">"</span><span class="p">,</span> <span class="nx">width</span><span class="p">)</span> <span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="dl">"</span><span class="s2">height</span><span class="dl">"</span><span class="p">,</span> <span class="nx">height</span><span class="p">);</span> <span class="nx">d3</span><span class="p">.</span><span class="nx">json</span><span class="p">(</span><span class="dl">"</span><span class="s2">cat-municipis.json</span><span class="dl">"</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">error</span><span class="p">,</span> <span class="nx">cat</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="k">throw</span> <span class="nx">error</span><span class="p">;</span> <span class="kd">var</span> <span class="nx">municipis</span> <span class="o">=</span> <span class="nx">topojson</span><span class="p">.</span><span class="nx">feature</span><span class="p">(</span><span class="nx">cat</span><span class="p">,</span> <span class="nx">cat</span><span class="p">.</span><span class="nx">objects</span><span class="p">.</span><span class="nx">municipis</span><span class="p">);</span> <span class="c1">// automatic center and scale (see http://bl.ocks.org/mbostock/4707858)</span> <span class="nx">projection</span> <span class="p">.</span><span class="nx">scale</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">.</span><span class="nx">translate</span><span class="p">([</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">]);</span> <span class="kd">var</span> <span class="nx">b</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">bounds</span><span class="p">(</span><span class="nx">municipis</span><span class="p">),</span> <span class="nx">s</span> <span class="o">=</span> <span class="p">.</span><span class="mi">95</span> <span class="o">/</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">max</span><span class="p">((</span><span class="nx">b</span><span class="p">[</span><span class="mi">1</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span> <span class="o">-</span> <span class="nx">b</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">0</span><span class="p">])</span> <span class="o">/</span> <span class="nx">width</span><span class="p">,</span> <span class="p">(</span><span class="nx">b</span><span class="p">[</span><span class="mi">1</span><span class="p">][</span><span class="mi">1</span><span class="p">]</span> <span class="o">-</span> <span class="nx">b</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">1</span><span class="p">])</span> <span class="o">/</span> <span class="nx">height</span><span class="p">),</span> <span class="nx">t</span> <span class="o">=</span> <span class="p">[(</span><span class="nx">width</span> <span class="o">-</span> <span class="nx">s</span> <span class="o">*</span> <span class="p">(</span><span class="nx">b</span><span class="p">[</span><span class="mi">1</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span> <span class="o">+</span> <span class="nx">b</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">0</span><span class="p">]))</span> <span class="o">/</span> <span class="mi">2</span><span class="p">,</span> <span class="p">(</span><span class="nx">height</span> <span class="o">-</span> <span class="nx">s</span> <span class="o">*</span> <span class="p">(</span><span class="nx">b</span><span class="p">[</span><span class="mi">1</span><span class="p">][</span><span class="mi">1</span><span class="p">]</span> <span class="o">+</span> <span class="nx">b</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">1</span><span class="p">]))</span> <span class="o">/</span> <span class="mi">2</span><span class="p">];</span> <span class="nx">projection</span> <span class="p">.</span><span class="nx">scale</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span> <span class="p">.</span><span class="nx">translate</span><span class="p">(</span><span class="nx">t</span><span class="p">);</span> <span class="nx">svg</span><span class="p">.</span><span class="nx">append</span><span class="p">(</span><span class="dl">"</span><span class="s2">path</span><span class="dl">"</span><span class="p">)</span> <span class="p">.</span><span class="nx">datum</span><span class="p">(</span><span class="nx">municipis</span><span class="p">)</span> <span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="dl">"</span><span class="s2">class</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">land</span><span class="dl">"</span><span class="p">)</span> <span class="p">.</span><span class="nx">attr</span><span class="p">(</span><span class="dl">"</span><span class="s2">d</span><span class="dl">"</span><span class="p">,</span> <span class="nx">path</span><span class="p">);</span> <span class="p">});</span> <span class="nt">&lt;/script&gt;</span></code></pre></figure> <p>…and produces:</p> <iframe src="https://teslabs.com/content/posts/topojson-catalonia/example-2.html" width="500" height="500" marginwidth="0" marginheight="0" scrolling="no"></iframe> <p><a href="https://teslabs.com/content/posts/topojson-catalonia/example-2.html" target="_blank">Open in a new window</a></p> <hr /> <p><strong>NOTE:</strong> Source maps are produced by ICGC and are subject to <a href="//www.icc.cat/conditions">terms of use</a>.</p> Fri, 20 Nov 2015 00:00:00 +0000 https://teslabs.com/articles/topojson-catalonia/ https://teslabs.com/articles/topojson-catalonia/