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