1 /*
2  *  staticpa_toolwin.cpp
3  *  PHD Guiding
4  *
5  *  Created by Ken Self
6  *  Copyright (c) 2017 Ken Self
7  *  All rights reserved.
8  *
9  *  This source code is distributed under the following "BSD" license
10  *  Redistribution and use in source and binary forms, with or without
11  *  modification, are permitted provided that the following conditions are met:
12  *    Redistributions of source code must retain the above copyright notice,
13  *     this list of conditions and the following disclaimer.
14  *    Redistributions in binary form must reproduce the above copyright notice,
15  *     this list of conditions and the following disclaimer in the
16  *     documentation and/or other materials provided with the distribution.
17  *    Neither the name of openphdguiding.org nor the names of its
18  *     contributors may be used to endorse or promote products derived from
19  *     this software without specific prior written permission.
20  *
21  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22  *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
25  *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26  *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27  *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28  *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31  *  POSSIBILITY OF SUCH DAMAGE.
32  *
33  */
34 
35 #include "phd.h"
36 #include "staticpa_tool.h"
37 #include "staticpa_toolwin.h"
38 
39 #include <wx/gbsizer.h>
40 #include <wx/valnum.h>
41 #include <wx/textwrapper.h>
42 
43 //==================================
BEGIN_EVENT_TABLE(StaticPaToolWin,wxFrame)44 BEGIN_EVENT_TABLE(StaticPaToolWin, wxFrame)
45 EVT_BUTTON(ID_INSTR, StaticPaToolWin::OnInstr)
46 EVT_CHOICE(ID_HEMI, StaticPaToolWin::OnHemi)
47 EVT_SPINCTRLDOUBLE(ID_HA, StaticPaToolWin::OnHa)
48 EVT_CHECKBOX(ID_MANUAL, StaticPaToolWin::OnManual)
49 EVT_CHECKBOX(ID_FLIP, StaticPaToolWin::OnFlip)
50 EVT_CHECKBOX(ID_ORBIT, StaticPaToolWin::OnOrbit)
51 EVT_CHOICE(ID_REFSTAR, StaticPaToolWin::OnRefStar)
52 EVT_BUTTON(ID_ROTATE, StaticPaToolWin::OnRotate)
53 EVT_BUTTON(ID_STAR2, StaticPaToolWin::OnStar2)
54 EVT_BUTTON(ID_STAR3, StaticPaToolWin::OnStar3)
55 EVT_BUTTON(ID_GOTO, StaticPaToolWin::OnGoto)
56 EVT_BUTTON(ID_CLEAR, StaticPaToolWin::OnClear)
57 EVT_BUTTON(ID_CLOSE, StaticPaToolWin::OnCloseBtn)
58 EVT_CLOSE(StaticPaToolWin::OnClose)
59 END_EVENT_TABLE()
60 
61 BEGIN_EVENT_TABLE(StaticPaToolWin::PolePanel, wxPanel)
62 EVT_PAINT(StaticPaToolWin::PolePanel::OnPaint)
63 EVT_LEFT_DCLICK(StaticPaToolWin::PolePanel::OnClick)
64 END_EVENT_TABLE()
65 
66 StaticPaToolWin::PolePanel::PolePanel(StaticPaToolWin* parent):
67     wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(320, 240), wxBU_AUTODRAW | wxBU_EXACTFIT),
68     paParent(parent)
69 {
70     m_origPt.x = 160;
71     m_origPt.y = 120;
72     m_currPt.x = 0;
73     m_currPt.y = 0;
74 }
75 
OnPaint(wxPaintEvent & evt)76 void StaticPaToolWin::PolePanel::OnPaint(wxPaintEvent& evt)
77 {
78     wxPaintDC dc(this);
79     paParent->CreateStarTemplate(dc, m_currPt);
80 }
Paint()81 void StaticPaToolWin::PolePanel::Paint()
82 {
83     wxClientDC dc(this);
84     paParent->CreateStarTemplate(dc, m_currPt);
85 }
OnClick(wxMouseEvent & evt)86 void StaticPaToolWin::PolePanel::OnClick(wxMouseEvent& evt)
87 {
88     const wxPoint pt = wxGetMousePosition();
89     const wxPoint mpt = GetScreenPosition();
90     wxPoint mousePt = pt - mpt - m_origPt; // Distance fron centre
91     m_currPt = m_currPt + mousePt; // Distance from origin
92     paParent->FillPanel();
93     Paint();
94 }
95 
CreateStaticPaToolWindow()96 wxWindow *StaticPaTool::CreateStaticPaToolWindow()
97 {
98     if (!pCamera || !pCamera->Connected)
99     {
100         wxMessageBox(_("Please connect a camera first."));
101         return 0;
102     }
103 
104     // confirm that image scale is specified
105     if (pFrame->GetCameraPixelScale() == 1.0)
106     {
107         bool confirmed = ConfirmDialog::Confirm(_(
108             "The Static Align tool is most effective when PHD2 knows your guide\n"
109             "scope focal length and camera pixel size.\n"
110             "\n"
111             "Enter your guide scope focal length on the Global tab in the Brain.\n"
112             "Enter your camera pixel size on the Camera tab in the Brain.\n"
113             "\n"
114             "Would you like to run the tool anyway?"),
115             "/rotate_tool_without_pixscale");
116 
117         if (!confirmed)
118         {
119             return 0;
120         }
121     }
122     if (pFrame->pGuider->IsCalibratingOrGuiding())
123     {
124         wxMessageBox(_("Please wait till Calibration is done and stop guiding"));
125         return 0;
126     }
127 
128     return new StaticPaToolWin();
129 }
PaintHelper(wxAutoBufferedPaintDCBase & dc,double scale)130 void StaticPaTool::PaintHelper(wxAutoBufferedPaintDCBase& dc, double scale)
131 {
132     StaticPaToolWin *win = static_cast<StaticPaToolWin *>(pFrame->pStaticPaTool);
133     if (win)
134     {
135         win->PaintHelper(dc, scale);
136     }
137 }
138 
NotifyStarLost()139 void StaticPaTool::NotifyStarLost()
140 {
141     // See if a Static PA is underway
142     StaticPaToolWin *win = static_cast<StaticPaToolWin *>(pFrame->pStaticPaTool);
143     if (win && win->IsAligning())
144     {
145         win->RotateFail(_("Static PA rotation failed - star lost"));
146     }
147 }
UpdateState()148 bool StaticPaTool::UpdateState()
149 {
150     StaticPaToolWin *win = static_cast<StaticPaToolWin *>(pFrame->pStaticPaTool);
151     if (win && win->IsAligning())
152     {
153         // Rotate the mount in RA a bit
154         if (!win->RotateMount())
155         {
156             return false;
157         }
158     }
159     return true;
160 }
161 
StaticPaToolWin()162 StaticPaToolWin::StaticPaToolWin()
163 : wxFrame(pFrame, wxID_ANY, _("Static Polar Alignment"), wxDefaultPosition, wxDefaultSize,
164 wxCAPTION | wxCLOSE_BOX | wxMINIMIZE_BOX | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxFRAME_FLOAT_ON_PARENT | wxFRAME_NO_TASKBAR)
165 {
166     m_numPos = 0;
167     m_devpx = 5;
168     ClearState();
169     m_aligning = false;
170 
171     //Fairly convoluted way to get the camera size in pixels
172     wxImage *pDispImg = pFrame->pGuider->DisplayedImage();
173     double scalefactor = pFrame->pGuider->ScaleFactor();
174     double xpx = pDispImg->GetWidth() / scalefactor;
175     double ypx = pDispImg->GetHeight() / scalefactor;
176     m_pxCentre.X = xpx/2;
177     m_pxCentre.Y = ypx/2;
178     m_pxScale = pFrame->GetCameraPixelScale();
179     // Fullsize is easier but the camera simulator does not set this.
180 //    wxSize camsize = pCamera->FullSize;
181     m_camWidth = pCamera->FullSize.GetWidth() == 0 ? xpx: pCamera->FullSize.GetWidth();
182 
183     m_camAngle = 0.0;
184     double camAngle_rad = 0.0;
185     m_flip = false;
186     if (pMount && pMount->IsConnected() && pMount->IsCalibrated())
187     {
188         camAngle_rad = pMount->xAngle();
189         Debug.AddLine(wxString::Format("StaticPA: Camera angle %.1f", degrees(camAngle_rad)));
190         wxString prefix = "/" + pMount->GetMountClassName() + "/calibration/";
191         int ipier = pConfig->Profile.GetInt(prefix + "pierSide", PIER_SIDE_UNKNOWN);
192         PierSide calPierSide = ipier == PIER_SIDE_EAST ? PIER_SIDE_EAST : ipier == PIER_SIDE_WEST ? PIER_SIDE_WEST : PIER_SIDE_UNKNOWN;
193         PierSide currPierSide = pPointingSource->SideOfPier();
194         Debug.AddLine(wxString::Format("StaticPA: calPierSide %s; currPierSide %s", pMount->PierSideStr(calPierSide), pMount->PierSideStr(currPierSide)));
195         if (currPierSide != calPierSide  && currPierSide != PIER_SIDE_UNKNOWN)
196         {
197             m_flip =  true;
198             Debug.AddLine(wxString::Format("StaticPA: Flipped Camera angle"));
199         }
200         m_camAngle = degrees(camAngle_rad);
201     }
202     // RA and Dec in J2000.0
203     c_SthStars = {
204         Star("A: sigma Oct", 317.19908, -88.9564, 4.3),
205         Star("B: HD99828", 165.91797, -89.2392, 7.5),
206         Star("C: HD125371", 241.45949, -89.3087, 7.8),
207         Star("D: HD92239", 142.27856, -89.3471, 8.0),
208         Star("E: HD90105", 130.52896, -89.4606, 7.2),
209         Star("F: BQ Oct", 218.86418, -89.7718, 6.8),
210         Star("G: HD99685", 149.13626, -89.7824, 7.8),
211         Star("H: HD98784", 134.64254, -89.8312, 8.9),
212     };
213     for (int is = 0; is < c_SthStars.size(); is++) {
214         PHD_Point radec_now = J2000Now(PHD_Point(c_SthStars.at(is).ra2000, c_SthStars.at(is).dec2000));
215         c_SthStars.at(is).ra = radec_now.X;
216         c_SthStars.at(is).dec = radec_now.Y;
217     }
218 
219     c_NthStars = {
220         Star("A: HD5914", 23.48114, 89.0155, 6.45),
221         Star("B: HD14369", 55.20640, 89.1048, 8.05),
222         Star("C: Polaris", 37.96089, 89.2643, 1.95),
223         Star("D: HD211455", 309.69879, 89.4065, 8.9),
224         Star("E: TYC-4629-33-1", 75.97399, 89.4207, 9.25),
225         Star("F: HD21070", 146.59109, 89.5695, 9.0),
226         Star("G: HD1687", 9.92515, 89.4443, 8.1),
227         Star("H: TYC-4629-37-1", 70.70722, 89.6301, 9.15),
228     };
229     for (int is = 0; is < c_NthStars.size(); is++) {
230         PHD_Point radec_now = J2000Now(PHD_Point(c_NthStars.at(is).ra2000, c_NthStars.at(is).dec2000));
231         c_NthStars.at(is).ra = radec_now.X;
232         c_NthStars.at(is).dec = radec_now.Y;
233     }
234 
235 
236     // get site lat/long from scope to determine hemisphere.
237     m_refStar = pConfig->Profile.GetInt("/StaticPaTool/RefStar", 0);
238     m_hemi = pConfig->Profile.GetInt("/StaticPaTool/Hemisphere", 1);
239     if (pPointingSource)
240     {
241         double lat, lon;
242         if (!pPointingSource->GetSiteLatLong(&lat, &lon))
243         {
244             m_hemi = lat >= 0 ? 1 : -1;
245         }
246     }
247     if (!pFrame->CaptureActive)
248     {
249         // loop exposures
250         SetStatusText(_("Start Looping..."));
251         pFrame->StartLoopingInteractive(_T("StaticPA:start"));
252     }
253     m_instr = false;
254     c_autoInstr = _(
255         "Slew to near the Celestial Pole.<br/>"
256         "Choose a Reference Star from the list.<br/>"
257         "Use the Star Map to help identify a Reference Star.<br/>"
258         "Select it as the guide star on the main display.<br/>"
259         "Click Rotate to start the alignment.<br/>"
260         "Wait for the adjustments to display.<br/>"
261         "Adjust your mount's altitude and azimuth as displayed.<br/>"
262         "Red=Altitude; Blue=Azimuth<br/>"
263         );
264     c_manualInstr = _(
265         "Slew to near the Celestial Pole.<br/>"
266         "Choose a Reference Star from the list.<br/>"
267         "Use the Star Map to help identify a Reference Star.<br/>"
268         "Select it as the guide star on the main display.<br/>"
269         "Click Get first position.<br/>"
270         "Slew at least 0h20m west in RA.<br/>"
271         "Ensure the Reference Star is still selected.<br/>"
272         "Click Get second position.<br/>"
273         "Repeat for the third position.<br/>"
274         "Wait for the adjustments to display.<br/>"
275         "Adjust your mount's altitude and azimuth to place "
276         "three reference stars on their orbits\n"
277         );
278 
279     // can mount slew?
280     m_auto = true;
281     m_drawOrbit = true;
282     m_canSlew = pPointingSource && pPointingSource->CanSlewAsync();
283     m_ha = 0.0;
284     if (!m_canSlew){
285         m_auto = false;
286         m_ha = 270.0;
287     }
288 
289     // Start window definition
290     SetSizeHints(wxDefaultSize, wxDefaultSize);
291 
292     // a vertical sizer holding everything
293     wxBoxSizer *topSizer = new wxBoxSizer(wxVERTICAL);
294 
295     // a horizontal box sizer for the bitmap and the instructions
296     wxBoxSizer *instrSizer = new wxBoxSizer(wxHORIZONTAL);
297     m_instructionsText = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxSize(320, 240), wxHW_DEFAULT_STYLE);
298     m_instructionsText->SetStandardFonts(8);
299     m_instructionsText->Hide();
300     /*
301 #ifdef __WXOSX__
302     m_instructionsText->SetFont(*wxSMALL_FONT);
303 #endif
304     */
305     instrSizer->Add(m_instructionsText, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 5);
306 
307     m_polePanel = new PolePanel(this);
308     instrSizer->Add(m_polePanel, 0, wxALIGN_CENTER_HORIZONTAL | wxALL | wxFIXED_MINSIZE, 5);
309 
310     m_instrButton = new wxButton(this, ID_INSTR, _("Instructions"), wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT);
311     instrSizer->Add(m_instrButton, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 0);
312 
313     topSizer->Add(instrSizer, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 5);
314 
315     // static box sizer holding the scope pointing controls
316     wxStaticBoxSizer *sbSizer = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, _("Alignment Parameters")), wxVERTICAL);
317 
318     // a grid box sizer for laying out scope pointing the controls
319     wxGridBagSizer *gbSizer = new wxGridBagSizer(0, 0);
320     gbSizer->SetFlexibleDirection(wxBOTH);
321     gbSizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
322 
323     wxStaticText *txt;
324 
325     // First row of grid
326     int gridRow = 0;
327     // Hour angle
328     txt = new wxStaticText(this, wxID_ANY, _("Hour Angle"));
329     txt->Wrap(-1);
330     gbSizer->Add(txt, wxGBPosition(gridRow, 0), wxGBSpan(1, 1), wxALL | wxALIGN_BOTTOM, 5);
331 
332     txt = new wxStaticText(this, wxID_ANY, _("Hemisphere"));
333     txt->Wrap(-1);
334     gbSizer->Add(txt, wxGBPosition(gridRow, 1), wxGBSpan(1, 1), wxALL | wxALIGN_BOTTOM, 5);
335 
336     // Alignment star specification
337     txt = new wxStaticText(this, wxID_ANY, _("Reference Star"));
338     txt->Wrap(-1);
339     gbSizer->Add(txt, wxGBPosition(gridRow, 2), wxGBSpan(1, 1), wxALL | wxALIGN_BOTTOM, 5);
340 
341     // Next row of grid
342     gridRow++;
343     m_hourAngleSpin = new wxSpinCtrlDouble(this, ID_HA, wxEmptyString, wxDefaultPosition, wxSize(10, -1), wxSP_ARROW_KEYS | wxSP_WRAP, 0.0, 24.0, m_ha/15.0, 0.1);
344     m_hourAngleSpin->SetToolTip(_("Set your scope hour angle"));
345     gbSizer->Add(m_hourAngleSpin, wxGBPosition(gridRow, 0), wxGBSpan(1, 1), wxEXPAND | wxALL | wxFIXED_MINSIZE, 5);
346     m_hourAngleSpin->SetDigits(1);
347 
348     wxArrayString hemi;
349     hemi.Add(_("North"));
350     hemi.Add(_("South"));
351     m_hemiChoice = new wxChoice(this, ID_HEMI, wxDefaultPosition, wxDefaultSize, hemi);
352     m_hemiChoice->SetToolTip(_("Select your hemisphere"));
353     gbSizer->Add(m_hemiChoice, wxGBPosition(gridRow, 1), wxGBSpan(1, 1), wxALL, 5);
354 
355     wxBoxSizer *refSizer = new wxBoxSizer(wxHORIZONTAL);
356 
357     m_refStarChoice = new wxChoice(this, ID_REFSTAR, wxDefaultPosition, wxDefaultSize);
358     m_refStarChoice->SetToolTip(_("Select the star used for checking alignment."));
359     refSizer->Add(m_refStarChoice, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 0);
360 
361     m_gotoButton = new wxButton(this, ID_GOTO, _(">"), wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT );
362     refSizer->Add(m_gotoButton, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 0);
363 
364     gbSizer->Add(refSizer, wxGBPosition(gridRow, 2), wxGBSpan(1, 1), wxALL, 5);
365 
366     // Next row of grid
367     gridRow++;
368     txt = new wxStaticText(this, wxID_ANY, _("Camera Angle"));
369     txt->Wrap(-1);
370     gbSizer->Add(txt, wxGBPosition(gridRow, 0), wxGBSpan(1, 1), wxALL | wxALIGN_BOTTOM, 5);
371 
372     txt = new wxStaticText(this, wxID_ANY, _("Arcsec/pixel"));
373     txt->Wrap(-1);
374     gbSizer->Add(txt, wxGBPosition(gridRow, 1), wxGBSpan(1, 1), wxALL | wxALIGN_BOTTOM, 5);
375 
376     m_manualCheck = new wxCheckBox(this, ID_MANUAL, _("Manual Slew"));
377     gbSizer->Add(m_manualCheck, wxGBPosition(gridRow, 2), wxGBSpan(1, 1), wxALL | wxALIGN_BOTTOM, 5);
378     m_manualCheck->SetValue(false);
379     m_manualCheck->SetToolTip(_("Manually slew the mount to three alignment positions"));
380 
381     // Next row of grid
382     gridRow++;
383 
384     m_camRotText = new wxTextCtrl(this, wxID_ANY, _T("--"), wxDefaultPosition, wxSize(10, -1), wxTE_READONLY);
385     m_camRotText->SetMinSize(wxSize(10, -1));
386     gbSizer->Add(m_camRotText, wxGBPosition(gridRow, 0), wxGBSpan(1, 1), wxEXPAND | wxALL, 5);
387 
388     m_camScaleText = new wxTextCtrl(this, wxID_ANY, _T("--"), wxDefaultPosition, wxSize(10, -1), wxTE_READONLY);
389     m_camRotText->SetMinSize(wxSize(10, -1));
390     gbSizer->Add(m_camScaleText, wxGBPosition(gridRow, 1), wxGBSpan(1, 1), wxEXPAND | wxALL, 5);
391 
392     m_star1Button = new wxButton(this, ID_ROTATE, _("Rotate"));
393     gbSizer->Add(m_star1Button, wxGBPosition(gridRow, 2), wxGBSpan(1, 1), wxEXPAND | wxALL, 5);
394 
395     // Next row of grid
396     gridRow++;
397 
398     m_flipCheck = new wxCheckBox(this, ID_FLIP, _("Flip camera"));
399     gbSizer->Add(m_flipCheck, wxGBPosition(gridRow, 0), wxGBSpan(1, 1), wxALL | wxALIGN_BOTTOM, 5);
400     m_flipCheck->SetValue(m_flip);
401     m_flipCheck->SetToolTip(_("Invert the camera angle"));
402 
403     m_star2Button = new wxButton(this, ID_STAR2, _("Get second position"));
404     gbSizer->Add(m_star2Button, wxGBPosition(gridRow, 2), wxGBSpan(1, 1), wxEXPAND | wxALL, 5);
405 
406     // Next row of grid
407     gridRow++;
408 
409     m_orbitCheck = new wxCheckBox(this, ID_ORBIT, _("Show Orbits"));
410     gbSizer->Add(m_orbitCheck, wxGBPosition(gridRow, 0), wxGBSpan(1, 1), wxALL | wxALIGN_BOTTOM, 5);
411     m_orbitCheck->SetValue(m_drawOrbit);
412     m_orbitCheck->SetToolTip(_("Show or hide the star orbits"));
413 
414     m_star3Button = new wxButton(this, ID_STAR3, _("Get third position"));
415     gbSizer->Add(m_star3Button, wxGBPosition(gridRow, 2), wxGBSpan(1, 1), wxEXPAND | wxALL, 5);
416 
417     // Next row of grid
418     gridRow++;
419 
420     m_clearButton = new wxButton(this, ID_CLEAR, _("Clear"), wxDefaultPosition, wxDefaultSize, 0);
421     gbSizer->Add(m_clearButton, wxGBPosition(gridRow, 1), wxGBSpan(1, 1), wxEXPAND | wxALL, 5);
422 
423     m_closeButton = new wxButton(this, ID_CLOSE, _("Close"), wxDefaultPosition, wxDefaultSize, 0);
424     gbSizer->Add(m_closeButton, wxGBPosition(gridRow, 2), wxGBSpan(1, 1), wxEXPAND | wxALL, 5);
425 
426     // add grid bag sizer to static sizer
427     sbSizer->Add(gbSizer, 1, wxALIGN_CENTER, 5);
428 
429     // add static sizer to top-level sizer
430     topSizer->Add(sbSizer, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 5);
431 
432     // add some padding below the static sizer
433     topSizer->Add(0, 3, 0, wxEXPAND, 3);
434 
435     m_notesLabel = new wxStaticText(this, wxID_ANY, _("Adjustment notes"));
436     m_notesLabel->Wrap(-1);
437     topSizer->Add(m_notesLabel, 0, wxEXPAND | wxTOP | wxLEFT, 8);
438 
439     m_notesText = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(-1, 54), wxTE_MULTILINE);
440     pFrame->RegisterTextCtrl(m_notesText);
441     topSizer->Add(m_notesText, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 5);
442     m_notesText->Bind(wxEVT_COMMAND_TEXT_UPDATED, &StaticPaToolWin::OnNotes, this);
443     m_notesText->SetValue(pConfig->Profile.GetString("/StaticPaTool/Notes", wxEmptyString));
444 
445     SetSizer(topSizer);
446 
447     m_statusBar = CreateStatusBar(1, wxST_SIZEGRIP, wxID_ANY);
448 
449     Layout();
450     topSizer->Fit(this);
451 
452     int xpos = pConfig->Global.GetInt("/StaticPaTool/pos.x", -1);
453     int ypos = pConfig->Global.GetInt("/StaticPaTool/pos.y", -1);
454     MyFrame::PlaceWindowOnScreen(this, xpos, ypos);
455 
456     FillPanel();
457 }
458 
~StaticPaToolWin()459 StaticPaToolWin::~StaticPaToolWin()
460 {
461     pFrame->pStaticPaTool = NULL;
462 }
463 
OnInstr(wxCommandEvent & evt)464 void StaticPaToolWin::OnInstr(wxCommandEvent& evt)
465 {
466     m_instr = !m_instr;
467     FillPanel();
468     return;
469 }
470 
OnHemi(wxCommandEvent & evt)471 void StaticPaToolWin::OnHemi(wxCommandEvent& evt)
472 {
473     int i_hemi = m_hemiChoice->GetSelection() <= 0 ? 1 : -1;
474     pConfig->Profile.SetInt("/StaticPaTool/Hemisphere", i_hemi);
475     if (i_hemi != m_hemi)
476     {
477         m_refStar = 0;
478         m_hemi = i_hemi;
479     }
480     FillPanel();
481 }
OnHa(wxSpinDoubleEvent & evt)482 void StaticPaToolWin::OnHa(wxSpinDoubleEvent& evt)
483 {
484     m_ha = evt.GetValue() * 15.0;
485     m_polePanel->Paint();
486 }
487 
OnManual(wxCommandEvent & evt)488 void StaticPaToolWin::OnManual(wxCommandEvent& evt)
489 {
490     m_auto = !m_manualCheck->IsChecked();
491     FillPanel();
492 }
493 
OnFlip(wxCommandEvent & evt)494 void StaticPaToolWin::OnFlip(wxCommandEvent& evt)
495 {
496     bool newFlipCheck = m_flipCheck->IsChecked();
497     if (newFlipCheck != m_flip)
498     {
499         m_polePanel->m_currPt = wxPoint(0, 0) - m_polePanel->m_currPt;
500     }
501     m_flip = newFlipCheck;
502     FillPanel();
503 }
504 
OnOrbit(wxCommandEvent & evt)505 void StaticPaToolWin::OnOrbit(wxCommandEvent& evt)
506 {
507     m_drawOrbit = m_orbitCheck->IsChecked();
508     FillPanel();
509 }
510 
OnRefStar(wxCommandEvent & evt)511 void StaticPaToolWin::OnRefStar(wxCommandEvent& evt)
512 {
513     int i_refStar = m_refStarChoice->GetSelection();
514     pConfig->Profile.SetInt("/StaticPaTool/RefStar", m_refStarChoice->GetSelection());
515 
516     m_refStar = i_refStar;
517 }
518 
OnNotes(wxCommandEvent & evt)519 void StaticPaToolWin::OnNotes(wxCommandEvent& evt)
520 {
521     pConfig->Profile.SetString("/StaticPaTool/Notes", m_notesText->GetValue());
522 }
523 
OnRotate(wxCommandEvent & evt)524 void StaticPaToolWin::OnRotate(wxCommandEvent& evt)
525 {
526     if (m_aligning && m_auto){ // STOP rotating
527         pPointingSource->AbortSlew();
528         m_aligning = false;
529         m_numPos = 0;
530         ClearState();
531         SetStatusText(_("Static alignment stopped"));
532         Debug.AddLine(wxString::Format("Static alignment stopped"));
533         FillPanel();
534         return;
535     }
536     if (pFrame->pGuider->IsCalibratingOrGuiding())
537     {
538         SetStatusText(_("Please wait till Calibration is done and/or stop guiding"));
539         return;
540     }
541     if (!pFrame->pGuider->IsLocked())
542     {
543         SetStatusText(_("Please select a star"));
544         return;
545     }
546     m_numPos = 1;
547     if(m_auto) ClearState();
548     m_aligning = true;
549     FillPanel();
550     return;
551 }
552 
OnStar2(wxCommandEvent & evt)553 void StaticPaToolWin::OnStar2(wxCommandEvent& evt)
554 {
555     m_numPos = 2;
556     m_aligning = true;
557 }
558 
OnStar3(wxCommandEvent & evt)559 void StaticPaToolWin::OnStar3(wxCommandEvent& evt)
560 {
561     m_numPos = 3;
562     m_aligning = true;
563 }
564 
OnGoto(wxCommandEvent & evt)565 void StaticPaToolWin::OnGoto(wxCommandEvent& evt)
566 {
567     // Get current star
568     // Convert current star Ra Dec to pixels
569     int is = m_refStar;
570     double scale = 320.0 / m_camWidth;
571 
572     PHD_Point stardeg = PHD_Point(m_poleStars->at(is).ra, m_poleStars->at(is).dec);
573     PHD_Point starpx = Radec2Px(stardeg);
574     m_polePanel->m_currPt = wxPoint(starpx.X*scale, starpx.Y*scale);
575     FillPanel();
576     return;
577 }
578 
OnClear(wxCommandEvent & evt)579 void StaticPaToolWin::OnClear(wxCommandEvent& evt)
580 {
581     if (IsCalced())
582     {
583         m_aligning = false;
584         m_numPos = 0;
585         ClearState();
586         SetStatusText(_("Static Polar alignment display cleared"));
587         Debug.AddLine(wxString::Format("Static PA display cleared"));
588         FillPanel();
589         return;
590     }
591 }
592 
OnCloseBtn(wxCommandEvent & evt)593 void StaticPaToolWin::OnCloseBtn(wxCommandEvent& evt)
594 {
595     wxCloseEvent dummy;
596     OnClose(dummy);
597 }
598 
OnClose(wxCloseEvent & evt)599 void StaticPaToolWin::OnClose(wxCloseEvent& evt)
600 {
601     if (IsAligning())
602     {
603         m_aligning = false;
604     }
605     // save the window position
606     int x, y;
607     GetPosition(&x, &y);
608     pConfig->Global.SetInt("/StaticPaTool/pos.x", x);
609     pConfig->Global.SetInt("/StaticPaTool/pos.y", y);
610     Debug.AddLine("Close StaticPaTool");
611     Destroy();
612 }
613 
FillPanel()614 void StaticPaToolWin::FillPanel()
615 {
616     if (m_instr)
617     {
618         m_instructionsText->Show();
619         m_polePanel->Hide();
620         m_instrButton->SetLabel(_("Star Map"));
621     }
622     else
623     {
624         m_instructionsText->Hide();
625         m_polePanel->Show();
626         m_instrButton->SetLabel(_("Instructions"));
627     }
628 
629     m_hourAngleSpin->Enable(true);
630 
631     if (!m_canSlew){
632         m_manualCheck->Hide();
633     }
634     m_manualCheck->SetValue(!m_auto);
635 
636     wxString html = wxString::Format("<html><body style=\"background-color:#cccccc;\">%s</body></html>", m_auto ? c_autoInstr : c_manualInstr);
637     m_instructionsText->SetPage(html);
638 
639     m_star1Button->SetLabel(_("Rotate"));
640     if (m_aligning) {
641         m_star1Button->SetLabel(_("Stop"));
642     }
643     m_star2Button->Hide();
644     m_star3Button->Hide();
645     m_hemiChoice->Enable(false);
646     m_hemiChoice->SetSelection(m_hemi > 0 ? 0 : 1);
647     if (!m_auto)
648     {
649         m_star1Button->SetLabel(_("Get first position"));
650         m_star2Button->Show();
651         m_star3Button->Show();
652         m_hemiChoice->Enable(true);
653     }
654     m_poleStars = m_hemi >= 0 ? &c_NthStars : &c_SthStars;
655     m_refStarChoice->Clear();
656     std::string starname;
657     for (int is = 0; is < m_poleStars->size(); is++) {
658         m_refStarChoice->AppendString(m_poleStars->at(is).name);
659     }
660     m_refStarChoice->SetSelection(m_refStar);
661     m_camScaleText->SetValue(wxString::Format("%.1f", m_pxScale));
662     m_camRotText->SetValue(wxString::Format("%.1f", m_camAngle));
663 
664     m_polePanel->Paint();
665     Layout();
666 }
667 
CalcRotationCentre(void)668 void StaticPaToolWin::CalcRotationCentre(void)
669 {
670     double x1, y1, x2, y2, x3, y3;
671     double cx, cy, cr;
672     x1 = m_pxPos[0].X;
673     y1 = m_pxPos[0].Y;
674     x2 = m_pxPos[1].X;
675     y2 = m_pxPos[1].Y;
676     UnsetState(0);
677 
678     if (!m_auto)
679     {
680         double a, b, c, e, f, g, i, j, k;
681         double m11, m12, m13, m14;
682         x3 = m_pxPos[2].X;
683         y3 = m_pxPos[2].Y;
684         Debug.AddLine(wxString::Format("StaticPA: Manual CalcCoR: P1(%.1f,%.1f); P2(%.1f,%.1f); P3(%.1f,%.1f)", x1, y1, x2, y2, x3, y3));
685         // |A| = aei + bfg + cdh -ceg -bdi -afh
686         // a b c
687         // d e f
688         // g h i
689         // a= x1^2+y1^2; b=x1; c=y1; d=1
690         // e= x2^2+y2^2; f=x2; g=y2; h=1
691         // i= x3^2+y3^2; j=x3; k=y3; l=1
692         //
693         // x0 = 1/2.|M12|/|M11|
694         // y0 = -1/2.|M13|/|M11|
695         // r = x0^2 + y0^2 + |M14| / |M11|
696         //
697         a = x1 * x1 + y1 * y1;
698         b = x1;
699         c = y1;
700         e = x2 * x2 + y2 * y2;
701         f = x2;
702         g = y2;
703         i = x3 * x3 + y3 * y3;
704         j = x3;
705         k = y3;
706         m11 = b * g + c * j + f * k - g * j - c * f - b * k;
707         m12 = a * g + c * i + e * k - g * i - c * e - a * k;
708         m13 = a * f + b * i + e * j - f * i - b * e - a * j;
709         m14 = a * f * k + b * g * i + c * e * j - c * f * i - b * e * k - a * g * j;
710         cx = (1. / 2.) * m12 / m11;
711         cy = (-1. / 2.) * m13 / m11;
712         cr = sqrt(cx * cx + cy *cy + m14 / m11);
713     }
714     else
715     {
716         Debug.AddLine(wxString::Format("StaticPA Auto CalcCoR: P1(%.1f,%.1f); P2(%.1f,%.1f); RA: %.1f %.1f", x1, y1, x2, y2, m_raPos[0] * 15., m_raPos[1] * 15.));
717         // Alternative algorithm based on two points and angle rotated
718         double radiff, theta2;
719         // Get RA change. For westward movement RA decreases.
720         // Invert to get image rotation (mult by -1)
721         // Convert to radians radians(mult by 15)
722         // Convert to RH system (mult by m_hemi)
723         // normalise to +/- PI
724         radiff = norm_angle(radians((m_raPos[0] - m_raPos[1])*15.0*m_hemi));
725 
726         theta2 = radiff / 2.0; // Half the image rotation for midpoint of chord
727         double lenchord = hypot((x1 - x2), (y1 - y2));
728         cr = fabs(lenchord / 2.0 / sin(theta2));
729         double lenbase = fabs(cr*cos(theta2));
730         // Calculate the slope of the chord in pixels
731         // We know the image is moving clockwise in NH and anti-clockwise in SH
732         // So subtract PI/2 in NH or add PI/2 in SH to get the slope to the CoR
733         // Invert y values as pixels are +ve downwards
734         double slopebase = atan2(y1 - y2, x2 - x1) - m_hemi* M_PI / 2.0;
735         cx = (x1 + x2) / 2.0 + lenbase * cos(slopebase);
736         cy = (y1 + y2) / 2.0 - lenbase * sin(slopebase); // subtract for pixels
737         Debug.AddLine(wxString::Format("StaticPA CalcCoR: radiff(deg): %.1f; cr: %.1f; slopebase(deg) %.1f", degrees(radiff), cr, degrees(slopebase)));
738     }
739     m_pxCentre.X = cx;
740     m_pxCentre.Y = cy;
741     m_radius = cr;
742 
743     wxImage *pDispImg = pFrame->pGuider->DisplayedImage();
744     double scalefactor = pFrame->pGuider->ScaleFactor();
745     int xpx = pDispImg->GetWidth() / scalefactor;
746     int ypx = pDispImg->GetHeight() / scalefactor;
747     m_dispSz[0] = xpx;
748     m_dispSz[1] = ypx;
749 
750     Debug.AddLine(wxString::Format("StaticPA CalcCoR: W:H:scale:angle %d: %d: %.1f %.1f", xpx, ypx, scalefactor, m_camAngle));
751 
752     // Distance and angle of CoR from centre of sensor
753     double cor_r = hypot(xpx / 2 - cx, ypx / 2 - cy);
754     double cor_a = degrees(atan2((ypx / 2 - cy), (xpx / 2 - cx)));
755     double rarot = -m_camAngle;
756     // Cone and Dec components of CoR vector
757     double dec_r = cor_r * sin(radians(cor_a - rarot));
758     m_DecCorr.X = -dec_r * sin(radians(rarot));
759     m_DecCorr.Y = dec_r * cos(radians(rarot));
760     double cone_r = cor_r * cos(radians(cor_a - rarot));
761     m_ConeCorr.X = cone_r * cos(radians(rarot));
762     m_ConeCorr.Y = cone_r * sin(radians(rarot));
763     SetState(0);
764     FillPanel();
765     return;
766 }
767 
CalcAdjustments(void)768 void StaticPaToolWin::CalcAdjustments(void)
769 {
770     // Caclulate pixel values for the alignment stars relative to the CoR
771     PHD_Point starpx, stardeg;
772     stardeg = PHD_Point(m_poleStars->at(m_refStar).ra, m_poleStars->at(m_refStar).dec);
773     starpx = Radec2Px(stardeg);
774     // Convert to display pixel values by adding the CoR pixel coordinates
775     double xt = starpx.X + m_pxCentre.X;
776     double yt = starpx.Y + m_pxCentre.Y;
777 
778     int idx = m_auto ? 1 : 2;
779     double xs = m_pxPos[idx].X;
780     double ys = m_pxPos[idx].Y;
781 
782     // Calculate the camera rotation from the Azimuth axis
783     // HA = LST - RA
784     // In NH HA decreases clockwise; RA increases clockwise
785     // "Up" is HA=0
786     // Sensor "up" is 90deg counterclockwise from mount RA plus rotation
787     // Star rotation is RAstar - RAmount
788 
789     // Alt angle aligns to HA=0, Azimuth (East) to HA = -90
790     // In home position Az aligns with Dec
791     // So at HA +/-90 (home pos) Alt rotation is 0 (HA+90)
792     // At the meridian, HA=0 Alt aligns with dec so Rotation is +/-90
793     // Let harot =  camera rotation from Alt axis
794     // Alt axis is at HA+90
795     // This is camera rotation from RA minus(?) LST angle
796     double    hcor_r = hypot((xt - xs), (yt - ys)); //xt,yt: target, xs,ys: measured
797     double    hcor_a = degrees(atan2((yt - ys), (xt - xs)));
798     double ra_hrs, dec_deg, st_hrs, ha_deg;
799     ha_deg = m_ha;
800     if (pPointingSource && !pPointingSource->GetCoordinates(&ra_hrs, &dec_deg, &st_hrs))
801     {
802         ha_deg = norm((st_hrs - ra_hrs)*15.0 + m_ha, 0, 360);
803     }
804     double rarot = -m_camAngle;
805     double harot = norm(rarot - (90 + ha_deg), 0, 360);
806     double hrot = norm(hcor_a - harot, 0, 360);
807 
808     double az_r = hcor_r * sin(radians(hrot));
809     double alt_r = hcor_r * cos(radians(hrot));
810 
811     m_AzCorr.X = -az_r * sin(radians(harot));
812     m_AzCorr.Y = az_r * cos(radians(harot));
813     m_AltCorr.X = alt_r * cos(radians(harot));
814     m_AltCorr.Y = alt_r * sin(radians(harot));
815     Debug.AddLine(wxString::Format("StaticPA CalcAdjust: Angles: rarot %.1f; ha_deg %.1f; m_ha %.1f; hcor_a %.1f; harot: %.1f", rarot, ha_deg, m_ha, hcor_a, harot));
816     Debug.AddLine(wxString::Format("StaticPA CalcAdjust: Errors(px): alt %.1f; az %.1f; tot %.1f", alt_r, az_r, hcor_r));
817     SetStatusText(wxString::Format(_("Polar Alignment Error (arcmin): Alt %.1f; Az %.1f Tot %.1f"),
818         fabs(alt_r)*m_pxScale/60, fabs(az_r)*m_pxScale/60, fabs(hcor_r)*m_pxScale/60));
819 }
820 
Radec2Px(const PHD_Point & radec)821 PHD_Point StaticPaToolWin::Radec2Px(const PHD_Point& radec)
822 {
823     // Convert dec to pixel radius
824     double r = (90.0 - fabs(radec.Y)) * 3600 / m_pxScale;
825 
826     // Rotate by calibration angle and HA f object taking into account mount rotation (HA)
827     double ra_hrs, dec_deg, ra_deg, st_hrs;
828     ra_deg = 0.0;
829     if (pPointingSource && !pPointingSource->GetCoordinates(&ra_hrs, &dec_deg, &st_hrs))
830     {
831         ra_deg = norm(ra_hrs * 15.0 + m_ha, 0, 360);
832     }
833     else
834     {
835         // If not a goto mount calculate ra_deg from LST assuming mount is in the home position (HA=18h)
836         tm j2000_info;
837         j2000_info.tm_year = 100;
838         j2000_info.tm_mon = 0;  // January is month 0
839         j2000_info.tm_mday = 1;
840         j2000_info.tm_hour = 12;
841         j2000_info.tm_min = 0;
842         j2000_info.tm_sec = 0;
843         j2000_info.tm_isdst = 0;
844         time_t j2000 = mktime(&j2000_info);
845         time_t nowutc = time(NULL);
846         tm *nowinfo = localtime(&nowutc);
847         time_t now = mktime(nowinfo);
848         double since = difftime(now, j2000) / 86400.0;
849         double hadeg = m_ha;
850         ra_deg = norm((280.46061837 + 360.98564736629 * since - hadeg),0,360);
851     }
852 
853     // Target hour angle - or rather the rotation needed to correct.
854     // HA = LST - RA
855     // In NH HA decreases clockwise; RA increases clockwise
856     // "Up" is HA=0
857     // Sensor "up" is 90deg counterclockwise from mount RA plus rotation
858     // Star rotation is RAstar - RAmount
859     double a1 = radec.X - (ra_deg - 90.0);
860     a1 = norm(a1, 0, 360);
861 
862     double l_camAngle;
863     l_camAngle = norm((m_flip ? m_camAngle + 180.0 : m_camAngle), 0, 360);
864     double a = l_camAngle - a1 * m_hemi;
865 
866     PHD_Point px(r * cos(radians(a)), -r * sin(radians(a)));
867     return px;
868 }
869 
J2000Now(const PHD_Point & radec)870 PHD_Point StaticPaToolWin::J2000Now(const PHD_Point& radec)
871 {
872     tm j2000_info;
873     j2000_info.tm_year = 100;
874     j2000_info.tm_mon = 0;  // January is month 0
875     j2000_info.tm_mday = 1;
876     j2000_info.tm_hour = 12;
877     j2000_info.tm_min = 0;
878     j2000_info.tm_sec = 0;
879     j2000_info.tm_isdst = 0;
880     time_t j2000 = mktime(&j2000_info);
881     time_t nowutc = time(NULL);
882     double JDnow = difftime(nowutc, j2000) / 86400.0;
883 
884     /*
885     This code is adapted from paper
886     Improvement of the IAU 2000 precession model
887     N. Capitaine, P. T. Wallace, J. Chapront
888     https://www.aanda.org/articles/aa/full/2005/10/aa1908/aa1908.html
889     The order of polynomial to be used was found to be t^5 and the precision of the coefficients 0.1 uas. The following series with
890     a 0.1 uas level of precision matches the canonical 4-rotation series to sub-microarcsecond accuracy over 4 centuries:
891     zetaA = 2.5976176 + 2306.0809506 t + 0.3019015 t^2 + 0.0179663 t^3 - 0.0000327 t^4 - 0.0000002 t^5
892     zedA = -2.5976176 + 2306.0803226 t + 1.0947790 t^2 + 0.0182273 t^3 + 0.0000470 t^4 - 0.0000003 t^5
893     thetaA = 2004.1917476 t - 0.4269353 t^2 - 0.0418251 t^3 - 0.0000601 t^4 - 0.0000001 t^5
894 
895     In this implementation we use coefficients up to t^3
896     */
897     double tnow = JDnow / 36525;  // JDNow is days since J2000.0 so no need to subtract JD2000
898     double t2 = pow(tnow, 2);
899     double t3 = pow(tnow, 3);
900     double zed, zeta, theta;            // arcseconds
901     double zedrad, zetarad, thetarad;
902     zeta = 2.5976176 + 2306.0809506*tnow + 0.3019015*t2 + 0.0179663*t3;
903     zetarad = radians(zeta / 3600);
904     zed = -2.5976176 + 2306.0803226*tnow + 1.0947790*t2 + 0.0182273*t3;
905     zedrad = radians(zed / 3600);
906     theta = 2004.1917476*tnow - 0.4269353*t2 - 0.0418251*t3;
907     thetarad = radians(theta / 3600);
908 
909 //  Build the transformation matrix
910     double Xx, Xy, Xz, Yx, Yy, Yz, Zx, Zy, Zz;
911     Xx = cos(zedrad)*cos(thetarad)*cos(zetarad) - sin(zedrad)*sin(zetarad);
912     Yx = -cos(zedrad)*cos(thetarad)*sin(zetarad) - sin(zedrad)*cos(zetarad);
913     Zx = -cos(zedrad)*sin(thetarad);
914     Xy = sin(zedrad)*cos(thetarad)*cos(zetarad) + cos(zedrad)*sin(zetarad);
915     Yy = -sin(zedrad)*cos(thetarad)*sin(zetarad) + cos(zedrad)*cos(zetarad);
916     Zy = -sin(zedrad)*sin(thetarad);
917     Xz = sin(thetarad)*cos(zetarad);
918     Yz = -sin(thetarad)*sin(zetarad);
919     Zz = cos(thetarad);
920 
921     // Transform coordinates;
922     double x0, y0, z0;
923     double x, y, z;
924     x0 = cos(radians(radec.Y)) * cos(radians(radec.X));
925     y0 = cos(radians(radec.Y)) * sin(radians(radec.X));
926     z0 = sin(radians(radec.Y));
927     x = Xx * x0 + Yx * y0 + Zx*z0;
928     y = Xy * x0 + Yy * y0 + Zy*z0;
929     z = Xz * x0 + Yz * y0 + Zz*z0;
930     double radeg, decdeg;
931     radeg = norm(degrees(atan2(y, x)), 0, 360);
932     decdeg = degrees(atan2(z, sqrt(1 - z * z)));
933     return PHD_Point(radeg, decdeg);
934 }
935 
PaintHelper(wxAutoBufferedPaintDCBase & dc,double scale)936 void StaticPaToolWin::PaintHelper(wxAutoBufferedPaintDCBase& dc, double scale)
937 {
938     double intens = 255;
939     dc.SetPen(wxPen(wxColour(0, intens, intens), 1, wxPENSTYLE_SOLID));
940     dc.SetBrush(*wxTRANSPARENT_BRUSH);
941 
942     for (int i = 0; i < 3; i++)
943     {
944         if (HasState(i+1))
945         {
946             dc.DrawCircle(m_pxPos[i].X*scale, m_pxPos[i].Y*scale, 12 * scale);
947         }
948     }
949     if (!IsCalced())
950     {
951         return;
952     }
953     dc.SetBrush(*wxTRANSPARENT_BRUSH);
954     dc.SetPen(wxPen(wxColor(intens, 0, intens), 1, wxPENSTYLE_DOT));
955     if (m_drawOrbit)
956     {
957         dc.DrawCircle(m_pxCentre.X*scale, m_pxCentre.Y*scale, m_radius*scale);
958     }
959 
960     // draw the centre of the circle as a red cross
961     dc.SetBrush(*wxTRANSPARENT_BRUSH);
962     dc.SetPen(wxPen(wxColor(255, 0, 0), 1, wxPENSTYLE_SOLID));
963     int region = 10;
964     dc.DrawLine((m_pxCentre.X - region)*scale, m_pxCentre.Y*scale, (m_pxCentre.X + region)*scale, m_pxCentre.Y*scale);
965     dc.DrawLine(m_pxCentre.X*scale, (m_pxCentre.Y - region)*scale, m_pxCentre.X*scale, (m_pxCentre.Y + region)*scale);
966 
967     // Show the centre of the display wth a grey cross
968     double xsc = m_dispSz[0] / 2;
969     double ysc = m_dispSz[1] / 2;
970     dc.SetPen(wxPen(wxColor(intens, intens, intens), 1, wxPENSTYLE_SOLID));
971     dc.DrawLine((xsc - region)*scale, ysc*scale, (xsc + region)*scale, ysc*scale);
972     dc.DrawLine(xsc*scale, (ysc - region)*scale, xsc*scale, (ysc + region)*scale);
973 
974     // Draw orbits for each reference star
975     // Caclulate pixel values for the reference stars
976     PHD_Point starpx, stardeg;
977     double radpx;
978     const std::string alpha = "ABCDEFGHIJKL";
979     const wxFont& SmallFont =
980 #if defined(__WXOSX__)
981         *wxSMALL_FONT;
982 #else
983         *wxSWISS_FONT;
984 #endif
985     dc.SetFont(SmallFont);
986     for (int is = 0; is < m_poleStars->size(); is++)
987     {
988         stardeg = PHD_Point(m_poleStars->at(is).ra, m_poleStars->at(is).dec);
989         starpx = Radec2Px(stardeg);
990         radpx = hypot(starpx.X, starpx.Y);
991         wxColor line_color = (is == m_refStar) ? wxColor(0, intens, 0) : wxColor(intens, intens, 0);
992         dc.SetPen(wxPen(line_color, 1, wxPENSTYLE_DOT));
993         if (m_drawOrbit)
994         {
995             dc.DrawCircle(m_pxCentre.X * scale, m_pxCentre.Y * scale, radpx * scale);
996         }
997         dc.SetPen(wxPen(line_color, 1, wxPENSTYLE_SOLID));
998         dc.DrawCircle((m_pxCentre.X + starpx.X) * scale, (m_pxCentre.Y + starpx.Y) * scale, region*scale);
999         dc.SetTextForeground(line_color);
1000         dc.DrawText(wxString::Format("%c", alpha[is]), (m_pxCentre.X + starpx.X + region) * scale, (m_pxCentre.Y + starpx.Y) * scale);
1001     }
1002     // Draw adjustment lines for centring the CoR on the display in blue (dec) and red (cone error)
1003     bool drawCone = false;
1004     if (drawCone){
1005         double xr = m_pxCentre.X * scale;
1006         double yr = m_pxCentre.Y * scale;
1007         dc.SetPen(wxPen(wxColor(intens, 0, 0), 1, wxPENSTYLE_SOLID));
1008         dc.DrawLine(xr, yr, xr + m_ConeCorr.X * scale, yr + m_ConeCorr.Y * scale);
1009         dc.SetPen(wxPen(wxColor(0, 0, intens), 1, wxPENSTYLE_SOLID));
1010         dc.DrawLine(xr + m_ConeCorr.X * scale, yr + m_ConeCorr.Y * scale,
1011             xr + m_DecCorr.X * scale + m_ConeCorr.X * scale,
1012             yr + m_DecCorr.Y * scale + m_ConeCorr.Y * scale);
1013         dc.SetPen(wxPen(wxColor(intens, intens, intens), 1, wxPENSTYLE_SOLID));
1014         dc.DrawLine(xr, yr, xr + m_DecCorr.X * scale + m_ConeCorr.X * scale,
1015             yr + m_DecCorr.Y * scale + m_ConeCorr.Y * scale);
1016     }
1017     // Draw adjustment lines for placing the guide star in its correct position relative to the CoR
1018     // Blue (azimuth) and Red (altitude)
1019     CalcAdjustments();
1020     bool drawCorr = true;
1021     if (drawCorr)
1022     {
1023         int idx;
1024         idx = m_auto ? 1 : 2;
1025         double xs = m_pxPos[idx].X * scale;
1026         double ys = m_pxPos[idx].Y * scale;
1027         dc.SetPen(wxPen(wxColor(intens, 0, 0), 1, wxPENSTYLE_DOT));
1028         dc.DrawLine(xs, ys, xs + m_AltCorr.X * scale, ys + m_AltCorr.Y * scale);
1029         dc.SetPen(wxPen(wxColor(0, 188.0*intens / 255.0, intens), 1, wxPENSTYLE_DOT));
1030         dc.DrawLine(xs + m_AltCorr.X * scale, ys + m_AltCorr.Y * scale,
1031             xs + m_AltCorr.X * scale + m_AzCorr.X * scale, ys + m_AzCorr.Y * scale + m_AltCorr.Y * scale);
1032         dc.SetPen(wxPen(wxColor(intens*2/3, intens*2/3, intens*2/3), 1, wxPENSTYLE_DOT));
1033         dc.DrawLine(xs, ys, xs + m_AltCorr.X * scale + m_AzCorr.X * scale,
1034             ys + m_AltCorr.Y * scale + m_AzCorr.Y * scale);
1035     }
1036 }
1037 
RotateMount()1038 bool StaticPaToolWin::RotateMount()
1039 {
1040     // Initially, assume an offset of 5.0 degrees of the camera from the CoR
1041     // Calculate how far to move in RA to get a detectable arc
1042     // Calculate the tangential ditance of that movement
1043     // Mark the starting position then rotate the mount
1044     SetStatusText(wxString::Format(_("Reading Star Position #%d"), m_numPos));
1045     Debug.AddLine(wxString::Format("StaticPA: Reading Star Pos#%d", m_numPos));
1046     if (m_numPos == 1)
1047     {
1048         //   Initially offset is 5 degrees;
1049         if (!SetParams(5.0)) // m_reqRot, m_reqStep for assumed 5.0 degree PA error
1050         {
1051             Debug.AddLine(wxString::Format("StaticPA: Error from SetParams"));
1052             return RotateFail(wxString::Format(_("Error setting rotation parameters: Stopping")));
1053         }
1054         Debug.AddLine(wxString::Format("StaticPA: Pos#1 m_reqRot=%.1f m_reqStep=%d", m_reqRot, m_reqStep));
1055         if (!SetStar(m_numPos))
1056         {
1057             Debug.AddLine(wxString::Format("StaticPA: Error from SetStar %d", m_numPos));
1058             return RotateFail(wxString::Format(_("Error reading star position #%d: Stopping"), m_numPos));
1059         }
1060         m_numPos++;
1061         if (!m_auto)
1062         {
1063             m_aligning = false;
1064             if (IsAligned())
1065             {
1066                 CalcRotationCentre();
1067             }
1068         }
1069         m_totRot = 0.0;
1070         m_nStep = 0;
1071         return true;
1072     }
1073     if (m_numPos == 2)
1074     {
1075         double theta = m_reqRot - m_totRot;
1076         // Once the mount has rotated theta degrees (as indicated by prevtheta);
1077         if (!m_auto)
1078         {
1079             if (!SetStar(m_numPos))
1080             {
1081                 Debug.AddLine(wxString::Format("StaticPA: Error from SetStar %d", m_numPos));
1082                 return RotateFail(wxString::Format(_("Error reading star position #%d: Stopping"), m_numPos));
1083             }
1084             m_numPos++;
1085             m_aligning = false;
1086             if (IsAligned()){
1087                 CalcRotationCentre();
1088             }
1089             return true;
1090         }
1091         SetStatusText(wxString::Format(_("Star Pos#2 Step=%d / %d Rotated=%.1f / %.1f deg"), m_nStep, m_reqStep, m_totRot, m_reqRot));
1092         Debug.AddLine(wxString::Format("StaticPA: Star Pos#2 m_nStep=%d / %d m_totRot=%.1f / %.1f deg", m_nStep, m_reqStep, m_totRot, m_reqRot));
1093         if (pPointingSource->Slewing())  // Wait till the mount has stopped
1094         {
1095             return true;
1096         }
1097         if (m_totRot < m_reqRot)
1098         {
1099             double newtheta = theta / (m_reqStep - m_nStep);
1100             if (!MoveWestBy(newtheta))
1101             {
1102                 Debug.AddLine(wxString::Format("StaticPA: Error from MoveWestBy at step %d", m_nStep));
1103                 return RotateFail(wxString::Format(_("Error moving west step %d: Stopping"), m_nStep));
1104             }
1105             m_totRot += newtheta;
1106         }
1107         else
1108         {
1109             if (!SetStar(m_numPos))
1110             {
1111                 Debug.AddLine(wxString::Format("StaticPA: Error from SetStar %d", m_numPos));
1112                 return RotateFail(wxString::Format(_("Error reading star position #%d: Stopping"), m_numPos));
1113             }
1114 
1115             // Calclate how far the mount moved compared to the expected movement;
1116             // This assumes that the mount was rotated through theta degrees;
1117             // So theta is the total rotation needed for the current offset;
1118             // And prevtheta is how we have already moved;
1119             // Recalculate the offset based on the actual movement;
1120             // CAUTION: This might end up in an endless loop.
1121             double actpix = hypot((m_pxPos[1].X - m_pxPos[0].X), (m_pxPos[1].Y - m_pxPos[0].Y));
1122             double actsec = actpix * m_pxScale;
1123             double actoffsetdeg = 90 - degrees(acos(actsec / 3600 / m_reqRot));
1124             Debug.AddLine(wxString::Format("StaticPA: Star Pos#2 actpix=%.1f actsec=%.1f m_pxScale=%.1f", actpix, actsec, m_pxScale));
1125 
1126             if (actoffsetdeg == 0)
1127             {
1128                 Debug.AddLine(wxString::Format("StaticPA: Star Pos#2 Mount did not move actoffsetdeg=%.1f", actoffsetdeg));
1129                 return RotateFail(wxString::Format(_("Star Pos#2 Mount did not move. Calculated polar offset=%.1f deg"), actoffsetdeg));
1130             }
1131             double prev_rotdg = m_reqRot;
1132             if (!SetParams(actoffsetdeg))
1133             {
1134                 Debug.AddLine(wxString::Format("StaticPA: Error from SetParams"));
1135                 return RotateFail(wxString::Format(_("Error setting rotation parameters: Stopping")));
1136             }
1137             if (m_reqRot <= prev_rotdg) // Moved far enough: show the adjustment chart
1138             {
1139                 m_numPos++;
1140                 m_nStep = 0;
1141                 m_totRot = 0.0;
1142                 m_aligning = false;
1143                 CalcRotationCentre();
1144             }
1145             else if (m_reqRot > 45)
1146             {
1147                 Debug.AddLine(wxString::Format("StaticPA: Pos#2 Too close to CoR actoffsetdeg=%.1f m_reqRot=%.1f", actoffsetdeg, m_reqRot));
1148                 return RotateFail(wxString::Format(_("Star is too close to CoR (%.1f deg) - try another reference star"), actoffsetdeg ));
1149             }
1150             else
1151             {
1152                 m_nStep = int(m_reqStep * m_totRot/m_reqRot);
1153                 Debug.AddLine(wxString::Format("StaticPA: Star Pos#2 m_nStep=%d / %d m_totRot=%.1f / %.1f", m_nStep, m_reqStep, m_totRot, m_reqRot));
1154             }
1155         }
1156         return true;
1157     }
1158     if (m_numPos == 3)
1159     {
1160         if (!m_auto)
1161         {
1162             if (!SetStar(m_numPos))
1163             {
1164                 Debug.AddLine(wxString::Format("StaticPA: Error from SetStar %d", m_numPos));
1165                 return RotateFail(wxString::Format(_("Error reading star position #%d: Stopping"), m_numPos));
1166             }
1167             m_numPos++;
1168             m_aligning = false;
1169             if (IsAligned()){
1170                 CalcRotationCentre();
1171             }
1172             return true;
1173         }
1174         m_numPos++;
1175         return true;
1176     }
1177     return true;
1178 }
1179 
RotateFail(const wxString & msg)1180 bool StaticPaToolWin::RotateFail(const wxString& msg)
1181 {
1182     SetStatusText(msg);
1183     m_aligning = false;
1184     if (m_auto) // STOP rotating
1185     {
1186         pPointingSource->AbortSlew();
1187         m_numPos = 0;
1188         ClearState();
1189         FillPanel();
1190     }
1191     return false;
1192 }
1193 
SetStar(int npos)1194 bool StaticPaToolWin::SetStar(int npos)
1195 {
1196     int idx = npos - 1;
1197     // Get X and Y coords from PHD2;
1198     // idx: 0=B, 1/2/3 = A[idx];
1199     double cur_dec, cur_st;
1200     UnsetState(npos);
1201     if (m_auto && pPointingSource->GetCoordinates(&m_raPos[idx], &cur_dec, &cur_st))
1202     {
1203         Debug.AddLine("StaticPA: SetStar failed to get scope coordinates");
1204         return false;
1205     }
1206     m_pxPos[idx].X = -1;
1207     m_pxPos[idx].Y = -1;
1208     PHD_Point star = pFrame->pGuider->CurrentPosition();
1209     if (!star.IsValid())
1210     {
1211         return false;
1212     }
1213     m_pxPos[idx] = star;
1214     SetState(npos);
1215     Debug.AddLine(wxString::Format("StaticPA: Setstar #%d %.0f, %.0f", npos, m_pxPos[idx].X, m_pxPos[idx].Y));
1216     SetStatusText(wxString::Format(_("Read Position #%d: %.0f, %.0f"), npos, m_pxPos[idx].X, m_pxPos[idx].Y));
1217     FillPanel();
1218     return true;
1219 }
1220 
SetParams(double newoffset)1221 bool StaticPaToolWin::SetParams(double newoffset)
1222 {
1223     double offsetdeg = newoffset;
1224     double m_offsetpx = offsetdeg * 3600 / m_pxScale;
1225     Debug.AddLine(wxString::Format("StaticPA:SetParams(newoffset=%.1f) m_pxScale=%.1f m_offsetpx=%.1f m_devpx=%.1f", newoffset, m_pxScale, m_offsetpx, m_devpx));
1226     if (m_offsetpx < m_devpx)
1227     {
1228         Debug.AddLine(wxString::Format("StaticPA: SetParams() Too close to CoR: m_offsetpx=%.1f m_devpx=%.1f", m_offsetpx, m_devpx));
1229         return false;
1230     }
1231     m_reqRot = degrees(acos(1 - m_devpx / m_offsetpx));
1232     double m_rotpx = m_reqRot * 3600.0 / m_pxScale * sin(radians(offsetdeg));
1233 
1234     int region = pFrame->pGuider->GetSearchRegion();
1235     m_reqStep = 1;
1236     if (m_rotpx > region)
1237     {
1238         m_reqStep = int(ceil(m_rotpx / region));
1239     }
1240     Debug.AddLine(wxString::Format("StaticPA: SetParams() m_reqRot=%.1f m_rotpx=%.1f m_reqStep=%d region=%d", m_reqRot, m_rotpx, m_reqStep, region));
1241     return true;
1242 }
MoveWestBy(double thetadeg)1243 bool StaticPaToolWin::MoveWestBy(double thetadeg)
1244 {
1245     double cur_ra, cur_dec, cur_st;
1246     if (pPointingSource->GetCoordinates(&cur_ra, &cur_dec, &cur_st))
1247     {
1248         Debug.AddLine("StaticPA: MoveWestBy failed to get scope coordinates");
1249         return false;
1250     }
1251     double slew_ra = norm_ra(cur_ra - thetadeg * 24.0 / 360.0);
1252 //    slew_ra = slew_ra - 24.0 * floor(slew_ra / 24.0);
1253     Debug.AddLine(wxString::Format("StaticPA: Slewing from RA hrs: %.3f to:%.3f", cur_ra, slew_ra));
1254     if (pPointingSource->SlewToCoordinatesAsync(slew_ra, cur_dec))
1255     {
1256         Debug.AddLine("StaticPA: MoveWestBy: async slew failed");
1257         return false;
1258     }
1259 
1260     m_nStep++;
1261     PHD_Point lockpos = pFrame->pGuider->CurrentPosition();
1262     if (pFrame->pGuider->SetLockPosToStarAtPosition(lockpos))
1263     {
1264         Debug.AddLine("StaticPA: MoveWestBy: Failed to lock star position");
1265         return false;
1266     }
1267     return true;
1268 }
1269 
CreateStarTemplate(wxDC & dc,const wxPoint & m_currPt)1270 void StaticPaToolWin::CreateStarTemplate(wxDC& dc, const wxPoint& m_currPt)
1271 {
1272     dc.SetBackground(*wxGREY_BRUSH);
1273     dc.Clear();
1274 
1275     double scale = 320.0 / m_camWidth;
1276     int region = 5;
1277 
1278     dc.SetTextForeground(*wxYELLOW);
1279     const wxFont& SmallFont =
1280 #if defined(__WXOSX__)
1281         *wxSMALL_FONT;
1282 #else
1283         *wxSWISS_FONT;
1284 #endif
1285     dc.SetFont(SmallFont);
1286 
1287     const std::string alpha = "ABCDEFGHIJKL";
1288     PHD_Point starpx, stardeg;
1289     double starsz, starmag;
1290     // Draw position of each alignment star
1291     for (int is = 0; is < m_poleStars->size(); is++)
1292         {
1293         stardeg = PHD_Point(m_poleStars->at(is).ra, m_poleStars->at(is).dec);
1294         starmag = m_poleStars->at(is).mag;
1295 
1296         starsz = 356.0*exp(-0.3*starmag) / m_pxScale;
1297         starpx = Radec2Px(stardeg);
1298         dc.SetPen(*wxYELLOW_PEN);
1299         dc.SetBrush(*wxYELLOW_BRUSH);
1300         wxPoint starPt = wxPoint(starpx.X*scale, starpx.Y*scale) - m_currPt + wxPoint(160, 120);
1301         dc.DrawCircle(starPt.x, starPt.y, starsz*scale);
1302         dc.DrawText(wxString::Format("%c", alpha[is]), starPt.x + starsz*scale, starPt.y);
1303     }
1304     // draw the pole as a red cross
1305     dc.SetBrush(*wxTRANSPARENT_BRUSH);
1306     dc.SetPen(wxPen(wxColor(255, 0, 0), 1, wxPENSTYLE_SOLID));
1307     dc.DrawLine(160- region*scale, 120, 160 + region*scale, 120);
1308     dc.DrawLine(160, 120 - region*scale, 160, 120 + region*scale);
1309     dc.DrawLine(160, 120, 160-m_currPt.x, 120-m_currPt.y);
1310     return;
1311 }
1312 
1313 
1314 
1315