/***************************************************************************
 *
 * Project:  OpenCPN
 * Purpose:  AIS Decoder Object
 * Author:   David Register
 *
 ***************************************************************************
 *   Copyright (C) 2010 by David S. Register                               *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,  USA.         *
 ***************************************************************************
 */

#ifdef __MINGW32__
#undef IPV6STRICT  // mingw FTBS fix:  missing struct ip_mreq
#include <windows.h>
#endif

#include "wx/wx.h"
#include "wx/tokenzr.h"
#include "wx/datetime.h"
#include <wx/wfstream.h>
#include <wx/imaglist.h>

#include <stdlib.h>
#include <math.h>
#include <time.h>

#include "cutil.h"
#include "FontMgr.h"
#include "dychart.h"
#include "ais.h"
#include "chart1.h"
#include "navutil.h"  // for Select
#include "georef.h"
#include "styles.h"
#include "datastream.h"
#include "Select.h"
#include "AIS_Decoder.h"
#include "AIS_Target_Data.h"
#include "AISTargetAlertDialog.h"
#include "AISTargetQueryDialog.h"
#include "wx28compat.h"
#include "OCPNPlatform.h"
#include "chcanv.h"

extern double gLat, gLon, gSog, gCog;
extern MyFrame *gFrame;
extern OCPNPlatform *g_Platform;

int g_ais_cog_predictor_width;
extern AIS_Decoder *g_pAIS;
extern AISTargetAlertDialog *g_pais_alert_dialog_active;
extern AISTargetQueryDialog *g_pais_query_dialog_active;

//    AIS Global configuration
extern bool g_bShowCOG;
extern double g_ShowCOG_Mins;
extern bool g_bHideMoored;
extern double g_ShowMoored_Kts;
extern bool g_bAISShowTracks;
extern bool g_bShowAreaNotices;
extern bool g_bDrawAISSize;
extern bool g_bDrawAISRealtime;
extern double g_AIS_RealtPred_Kts;
extern bool g_bShowAISName;
extern int g_Show_Target_Name_Scale;
extern bool g_bInlandEcdis;

extern int g_ais_alert_dialog_x, g_ais_alert_dialog_y;
extern int g_ais_alert_dialog_sx, g_ais_alert_dialog_sy;

extern bool g_bShowScaled;

int g_ShowScaled_Num;
int ImportanceSwitchPoint = 100;
int g_ScaledNumWeightSOG;
int g_ScaledNumWeightCPA;
int g_ScaledNumWeightTCPA;
int g_ScaledNumWeightRange;
int g_ScaledNumWeightSizeOfT;
int g_ScaledSizeMinimal;

extern ArrayOfMMSIProperties g_MMSI_Props_Array;

extern float g_ShipScaleFactorExp;

float AISImportanceSwitchPoint = 0.0;

#if !defined(NAN)
static const long long lNaN = 0xfff8000000000000;
#define NAN (*(double *)&lNaN)
#endif

#include <wx/listimpl.cpp>
WX_DEFINE_LIST(AISTargetTrackList);

wxString ais_get_status(int index) {
  static const wxString ais_status[] = {
      _("Underway using Engine"),
      _("At Anchor"),
      _("Not Under Command"),
      _("Restricted Manoeuvrability"),
      _("Constrained by draught"),
      _("Moored"),
      _("Aground"),
      _("Engaged in Fishing"),
      _("Underway Sailing"),
      _("High Speed Craft"),
      _("Wing In Ground Effect"),
      _("Power-driven vessel towing astern (regional use)"),
      _("Power-driven vessel pushing ahead or towing alongside (regional use)"),
      _("Reserved 13"),
      _("Reserved 14"),
      _("Undefined"),
      _("AtoN Virtual"),
      _("AtoN Virtual (On Position)"),
      _("AtoN Virtual (Off Position)"),
      _("AtoN Real"),
      _("AtoN Real (On Position)"),
      _("AtoN Real(Off Position)")};

  return ais_status[index];
}

wxString ais_get_type(int index) {
  static const wxString ais_type[] = {
      _("Fishing Vessel"),                            // 30        0
      _("Towing Vessel"),                             // 31        1
      _("Towing Vessel, Long"),                       // 32        2
      _("Dredger"),                                   // 33        3
      _("Diving Ops Vessel"),                         // 34        4
      _("Military Vessel"),                           // 35        5
      _("Sailing Vessel"),                            // 36        6
      _("Pleasure craft"),                            // 37        7
      _("High Speed Craft"),                          // 4x        8
      _("Pilot Vessel"),                              // 50        9
      _("Search and Rescue Vessel"),                  // 51        10
      _("Tug"),                                       // 52        11
      _("Port Tender"),                               // 53        12
      _("Pollution Control Vessel"),                  // 54        13
      _("Law Enforcement Vessel"),                    // 55        14
      _("Medical Transport"),                         // 58        15
      _("Passenger Ship"),                            // 6x        16
      _("Cargo Ship"),                                // 7x        17
      _("Tanker"),                                    // 8x        18
      _("Unknown"),                                   //          19
      _("Unspecified"),                               // 00        20
      _("Reference Point"),                           // 01        21
      _("RACON"),                                     // 02        22
      _("Fixed Structure"),                           // 03        23
      _("Spare"),                                     // 04        24
      _("Light"),                                     // 05        25
      _("Light w/Sectors"),                           // 06        26
      _("Leading Light Front"),                       // 07        27
      _("Leading Light Rear"),                        // 08        28
      _("Cardinal N Beacon"),                         // 09        29
      _("Cardinal E Beacon"),                         // 10        30
      _("Cardinal S Beacon"),                         // 11        31
      _("Cardinal W Beacon"),                         // 12        32
      _("Beacon, Port Hand"),                         // 13        33
      _("Beacon, Starboard Hand"),                    // 14        34
      _("Beacon, Preferred Channel Port Hand"),       // 15        35
      _("Beacon, Preferred Channel Starboard Hand"),  // 16        36
      _("Beacon, Isolated Danger"),                   // 17        37
      _("Beacon, Safe Water"),                        // 18        38
      _("Beacon, Special Mark"),                      // 19        39
      _("Cardinal Mark N"),                           // 20        40
      _("Cardinal Mark E"),                           // 21        41
      _("Cardinal Mark S"),                           // 22        42
      _("Cardinal Mark W"),                           // 23        43
      _("Port Hand Mark"),                            // 24        44
      _("Starboard Hand Mark"),                       // 25        45
      _("Preferred Channel Port Hand"),               // 26        46
      _("Preferred Channel Starboard Hand"),          // 27        47
      _("Isolated Danger"),                           // 28        48
      _("Safe Water"),                                // 29        49
      _("Special Mark"),                              // 30        50
      _("Light Vessel/Rig"),                          // 31        51
      _("GpsGate Buddy"),                             // xx        52
      _("Position Report"),                           // xx        53
      _("Distress"),                                  // xx        54
      _("ARPA radar target"),                         // xx        55
      _("APRS Position Report")                       // xx        56
  };

  return ais_type[index];
}

wxString ais_get_short_type(int index) {
  static const wxString short_ais_type[] = {
      _("F/V"),       // 30        0
      _("Tow"),       // 31        1
      _("Long Tow"),  // 32        2
      _("Dredge"),    // 33        3
      _("D/V"),       // 34        4
      _("Mil/V"),     // 35        5
      _("S/V"),       // 36        6
      _("Yat"),       // 37        7
      _("HSC"),       // 4x        8
      _("P/V"),       // 50        9
      _("SAR/V"),     // 51        10
      _("Tug"),       // 52        11
      _("Tender"),    // 53        12
      _("PC/V"),      // 54        13
      _("LE/V"),      // 55        14
      _("Med/V"),     // 58        15
      _("Pass/V"),    // 6x        16
      _("M/V"),       // 7x        17
      _("M/T"),       // 8x        18
      _("?"),         //          19

      _("AtoN"),          // 00        20
      _("Ref. Pt"),       // 01        21
      _("RACON"),         // 02        22
      _("Fix.Struct."),   // 03        23
      _("?"),             // 04        24
      _("Lt"),            // 05        25
      _("Lt sect."),      // 06        26
      _("Ldg Lt Front"),  // 07        27
      _("Ldg Lt Rear"),   // 08        28
      _("Card. N"),       // 09        29
      _("Card. E"),       // 10        30
      _("Card. S"),       // 11        31
      _("Card. W"),       // 12        32
      _("Port"),          // 13        33
      _("Stbd"),          // 14        34
      _("Pref. Chnl"),    // 15        35
      _("Pref. Chnl"),    // 16        36
      _("Isol. Dngr"),    // 17        37
      _("Safe Water"),    // 18        38
      _("Special"),       // 19        39
      _("Card. N"),       // 20        40
      _("Card. E"),       // 21        41
      _("Card. S"),       // 22        42
      _("Card. W"),       // 23        43
      _("Port Hand"),     // 24        44
      _("Stbd Hand"),     // 25        45
      _("Pref. Chnl"),    // 26        46
      _("Pref. Chnl"),    // 27        47
      _("Isol. Dngr"),    // 28        48
      _("Safe Water"),    // 29        49
      _("Special"),       // 30        50
      _("LtV/Rig"),       // 31        51
      _("Buddy"),         // xx        52
      _("DSC"),           // xx        53
      _("Distress"),      // xx        54
      _("ARPA"),          // xx        55
      _("APRS")           // xx        56
  };

  return short_ais_type[index];
}

wxString ais8_001_22_notice_names[] = {
    // 128] = {
    _("Caution Area: Marine mammals habitat (implies whales NOT "
      "observed)"),  // 0 - WARNING: extra text by Kurt
    _("Caution Area: Marine mammals in area - reduce speed"),        // 1
    _("Caution Area: Marine mammals in area - stay clear"),          // 2
    _("Caution Area: Marine mammals in area - report sightings"),    // 3
    _("Caution Area: Protected habitat - reduce speed"),             // 4
    _("Caution Area: Protected habitat - stay clear"),               // 5
    _("Caution Area: Protected habitat - no fishing or anchoring"),  // 6
    _("Caution Area: Derelicts (drifting objects)"),                 // 7
    _("Caution Area: Traffic congestion"),                           // 8
    _("Caution Area: Marine event"),                                 // 9
    _("Caution Area: Divers down"),                                  // 10
    _("Caution Area: Swim area"),                                    // 11
    _("Caution Area: Dredge operations"),                            // 12
    _("Caution Area: Survey operations"),                            // 13
    _("Caution Area: Underwater operation"),                         // 14
    _("Caution Area: Seaplane operations"),                          // 15
    _("Caution Area: Fishery - nets in water"),                      // 16
    _("Caution Area: Cluster of fishing vessels"),                   // 17
    _("Caution Area: Fairway closed"),                               // 18
    _("Caution Area: Harbour closed"),                               // 19
    _("Caution Area: Risk (define in Associated text field)"),       // 20
    _("Caution Area: Underwater vehicle operation"),                 // 21
    _("(reserved for future use)"),                                  // 22
    _("Environmental Caution Area: Storm front (line squall)"),      // 23
    _("Environmental Caution Area: Hazardous sea ice"),              // 24
    _("Environmental Caution Area: Storm warning (storm cell or line "
      "of storms)"),                              // 25
    _("Environmental Caution Area: High wind"),   // 26
    _("Environmental Caution Area: High waves"),  // 27
    _("Environmental Caution Area: Restricted visibility (fog, rain, "
      "etc.)"),                                                      // 28
    _("Environmental Caution Area: Strong currents"),                // 29
    _("Environmental Caution Area: Heavy icing"),                    // 30
    _("(reserved for future use)"),                                  // 31
    _("Restricted Area: Fishing prohibited"),                        // 32
    _("Restricted Area: No anchoring."),                             // 33
    _("Restricted Area: Entry approval required prior to transit"),  // 34
    _("Restricted Area: Entry prohibited"),                          // 35
    _("Restricted Area: Active military OPAREA"),                    // 36
    _("Restricted Area: Firing - danger area."),                     // 37
    _("Restricted Area: Drifting Mines"),                            // 38
    _("(reserved for future use)"),                                  // 39
    _("Anchorage Area: Anchorage open"),                             // 40
    _("Anchorage Area: Anchorage closed"),                           // 41
    _("Anchorage Area: Anchoring prohibited"),                       // 42
    _("Anchorage Area: Deep draft anchorage"),                       // 43
    _("Anchorage Area: Shallow draft anchorage"),                    // 44
    _("Anchorage Area: Vessel transfer operations"),                 // 45
    _("(reserved for future use)"),                                  // 46
    _("(reserved for future use)"),                                  // 47
    _("(reserved for future use)"),                                  // 48
    _("(reserved for future use)"),                                  // 49
    _("(reserved for future use)"),                                  // 50
    _("(reserved for future use)"),                                  // 51
    _("(reserved for future use)"),                                  // 52
    _("(reserved for future use)"),                                  // 53
    _("(reserved for future use)"),                                  // 54
    _("(reserved for future use)"),                                  // 55
    _("Security Alert - Level 1"),                                   // 56
    _("Security Alert - Level 2"),                                   // 57
    _("Security Alert - Level 3"),                                   // 58
    _("(reserved for future use)"),                                  // 59
    _("(reserved for future use)"),                                  // 60
    _("(reserved for future use)"),                                  // 61
    _("(reserved for future use)"),                                  // 62
    _("(reserved for future use)"),                                  // 63
    _("Distress Area: Vessel disabled and adrift"),                  // 64
    _("Distress Area: Vessel sinking"),                              // 65
    _("Distress Area: Vessel abandoning ship"),                      // 66
    _("Distress Area: Vessel requests medical assistance"),          // 67
    _("Distress Area: Vessel flooding"),                             // 68
    _("Distress Area: Vessel fire/explosion"),                       // 69
    _("Distress Area: Vessel grounding"),                            // 70
    _("Distress Area: Vessel collision"),                            // 71
    _("Distress Area: Vessel listing/capsizing"),                    // 72
    _("Distress Area: Vessel under assault"),                        // 73
    _("Distress Area: Person overboard"),                            // 74
    _("Distress Area: SAR area"),                                    // 75
    _("Distress Area: Pollution response area"),                     // 76
    _("(reserved for future use)"),                                  // 77
    _("(reserved for future use)"),                                  // 78
    _("(reserved for future use)"),                                  // 79
    _("Instruction: Contact VTS at this point/juncture"),            // 80
    _("Instruction: Contact Port Administration at this "
      "point/juncture"),                                          // 81
    _("Instruction: Do not proceed beyond this point/juncture"),  // 82
    _("Instruction: Await instructions prior to proceeding beyond this "
      "point/juncture"),                                 // 83
    _("Proceed to this location - await instructions"),  // 84
    _("Clearance granted - proceed to berth"),           // 85
    _("(reserved for future use)"),                      // 86
    _("(reserved for future use)"),                      // 87
    _("Information: Pilot boarding position"),           // 88
    _("Information: Icebreaker waiting area"),           // 89
    _("Information: Places of refuge"),                  // 90
    _("Information: Position of icebreakers"),           // 91
    _("Information: Location of response units"),        // 92
    _("VTS active target"),                              // 93
    _("Rogue or suspicious vessel"),                     // 94
    _("Vessel requesting non-distress assistance"),      // 95
    _("Chart Feature: Sunken vessel"),                   // 96
    _("Chart Feature: Submerged object"),                // 97
    _("Chart Feature: Semi-submerged object"),           // 98
    _("Chart Feature: Shoal area"),                      // 99
    _("Chart Feature: Shoal area due north"),            // 100
    _("Chart Feature: Shoal area due east"),             // 101
    _("Chart Feature: Shoal area due south"),            // 102
    _("Chart Feature: Shoal area due west"),             // 103
    _("Chart Feature: Channel obstruction"),             // 104
    _("Chart Feature: Reduced vertical clearance"),      // 105
    _("Chart Feature: Bridge closed"),                   // 106
    _("Chart Feature: Bridge partially open"),           // 107
    _("Chart Feature: Bridge fully open"),               // 108
    _("(reserved for future use)"),                      // 109
    _("(reserved for future use)"),                      // 110
    _("(reserved for future use)"),                      // 111
    _("Report from ship: Icing info"),                   // 112
    _("(reserved for future use)"),                      // 113
    _("Report from ship: Miscellaneous information - define in "
      "Associated text field"),                    // 114
    _("(reserved for future use)"),                // 115
    _("(reserved for future use)"),                // 116
    _("(reserved for future use)"),                // 117
    _("(reserved for future use)"),                // 118
    _("(reserved for future use)"),                // 119
    _("Route: Recommended route"),                 // 120
    _("Route: Alternative route"),                 // 121
    _("Route: Recommended route through ice"),     // 122
    _("(reserved for future use)"),                // 123
    _("(reserved for future use)"),                // 124
    _("Other - Define in associated text field"),  // 125
    _("Cancellation - cancel area as identified by Message Linkage "
      "ID"),                  // 126
    _("Undefined (default)")  //, // 127
};

static bool GetCanvasPointPix(ViewPort &vp, ChartCanvas *cp, double rlat,
                              double rlon, wxPoint *r) {
  if (cp != NULL) {
    return cp->GetCanvasPointPix(rlat, rlon, r);
  }
  *r = vp.GetPixFromLL(rlat, rlon);
  return true;
}

wxString trimAISField(char *data) {
  //  Clip any unused characters (@) from data

  wxString field = wxString::From8BitData(data);
  while (field.Right(1) == '@' || field.Right(1) == ' ') field.RemoveLast();

  //  And remove any leading spaces to properly sort and display
  field.Trim(false);

  return field;
}

static wxPoint transrot(wxPoint pt, float sin_theta, float cos_theta,
                        wxPoint offset = wxPoint(0, 0)) {
  wxPoint ret;
  float px = (float)(pt.x * sin_theta) + (float)(pt.y * cos_theta);
  float py = (float)(pt.y * sin_theta) - (float)(pt.x * cos_theta);
  ret.x = (int)wxRound(px);
  ret.y = (int)wxRound(py);
  ret.x += offset.x;
  ret.y += offset.y;

  return ret;
}

static void transrot_pts(int n, wxPoint *pt, float sin_theta, float cos_theta,
                         wxPoint offset = wxPoint(0, 0)) {
  for (int i = 0; i < n; i++)
    pt[i] = transrot(pt[i], sin_theta, cos_theta, offset);
}

void AISDrawAreaNotices(ocpnDC &dc, ViewPort &vp, ChartCanvas *cp) {
  if (cp == NULL) return;
  if (!g_pAIS || !cp->GetShowAIS() || !g_bShowAreaNotices) return;

  wxDateTime now = wxDateTime::Now();
  now.MakeGMT();

  bool b_pens_set = false;
  wxPen pen_save;
  wxBrush brush_save;
  wxColour yellow;
  wxColour green;
  wxPen pen;
  wxBrush *yellow_brush = wxTheBrushList->FindOrCreateBrush(
      wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT);
  wxBrush *green_brush = wxTheBrushList->FindOrCreateBrush(
      wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT);
  ;
  wxBrush *brush;

  AIS_Target_Hash *current_targets = g_pAIS->GetAreaNoticeSourcesList();

  float vp_scale = vp.view_scale_ppm;

  for (AIS_Target_Hash::iterator target = current_targets->begin();
       target != current_targets->end(); ++target) {
    AIS_Target_Data *target_data = target->second;
    if (!target_data->area_notices.empty()) {
      if (!b_pens_set) {
        pen_save = dc.GetPen();
        brush_save = dc.GetBrush();

        yellow = GetGlobalColor(_T ( "YELO1" ));
        yellow.Set(yellow.Red(), yellow.Green(), yellow.Blue(), 64);

        green = GetGlobalColor(_T ( "GREEN4" ));
        green.Set(green.Red(), green.Green(), green.Blue(), 64);

        pen.SetColour(yellow);
        pen.SetWidth(2);

        yellow_brush = wxTheBrushList->FindOrCreateBrush(
            yellow, wxBRUSHSTYLE_CROSSDIAG_HATCH);
        green_brush =
            wxTheBrushList->FindOrCreateBrush(green, wxBRUSHSTYLE_TRANSPARENT);
        brush = yellow_brush;

        b_pens_set = true;
      }

      for (AIS_Area_Notice_Hash::iterator ani =
               target_data->area_notices.begin();
           ani != target_data->area_notices.end(); ++ani) {
        Ais8_001_22 &area_notice = ani->second;

        if (area_notice.expiry_time > now) {
          std::vector<wxPoint> points;
          bool draw_polygon = false;

          switch (area_notice.notice_type) {
            case 0:
              pen.SetColour(green);
              brush = green_brush;
              break;
            case 1:
              pen.SetColour(yellow);
              brush = yellow_brush;
              break;
            default:
              pen.SetColour(yellow);
              brush = yellow_brush;
          }
          dc.SetPen(pen);
          dc.SetBrush(*brush);

          for (Ais8_001_22_SubAreaList::iterator sa =
                   area_notice.sub_areas.begin();
               sa != area_notice.sub_areas.end(); ++sa) {
            switch (sa->shape) {
              case AIS8_001_22_SHAPE_CIRCLE: {
                wxPoint target_point;
                GetCanvasPointPix(vp, cp, sa->latitude, sa->longitude,
                                  &target_point);
                points.push_back(target_point);
                if (sa->radius_m > 0.0)
                  dc.DrawCircle(target_point, sa->radius_m * vp_scale);
                break;
              }
              case AIS8_001_22_SHAPE_POLYGON:
                draw_polygon = true;
                // FALL THROUGH
              case AIS8_001_22_SHAPE_POLYLINE: {
                double lat = sa->latitude;
                double lon = sa->longitude;
                for (int i = 0; i < 4; ++i) {
                  ll_gc_ll(lat, lon, sa->angles[i], sa->dists_m[i] / 1852.0,
                           &lat, &lon);
                  wxPoint target_point;
                  GetCanvasPointPix(vp, cp, lat, lon, &target_point);
                  points.push_back(target_point);
                }
              }
            }
          }
          if (draw_polygon) dc.DrawPolygon(points.size(), &points.front());
        }
      }
    }
  }

  if (b_pens_set) {
    dc.SetPen(pen_save);
    dc.SetBrush(brush_save);
  }
}

static void TargetFrame(ocpnDC &dc, wxPen pen, int x, int y, int radius) {
  //    Constants?
  int gap2 = 2 * radius / 6;

  wxPen pen_save = dc.GetPen();

  dc.SetPen(pen);

  dc.DrawLine(x - radius, y + gap2, x - radius, y + radius);
  dc.DrawLine(x - radius, y + radius, x - gap2, y + radius);
  dc.DrawLine(x + gap2, y + radius, x + radius, y + radius);
  dc.DrawLine(x + radius, y + radius, x + radius, y + gap2);
  dc.DrawLine(x + radius, y - gap2, x + radius, y - radius);
  dc.DrawLine(x + radius, y - radius, x + gap2, y - radius);
  dc.DrawLine(x - gap2, y - radius, x - radius, y - radius);
  dc.DrawLine(x - radius, y - radius, x - radius, y - gap2);

  dc.SetPen(pen_save);
}

static void AtoN_Diamond(ocpnDC &dc, int x, int y, int radius,
                         AIS_Target_Data *td) {
  //    Constants?
  wxPen pen_save = dc.GetPen();

  wxPen aton_DrawPen;
  wxPen aton_WhiteBorderPen;
  wxBrush aton_Brush;

  int rad1a = radius / 2;  // size off topmarks of AtoN
  int rad2a = radius / 4;
  int rad3a =
      rad1a -
      1;  // slightly smaller size off topmarks to look better for the eye

  // Set the Pen for what is needed
  if ((td->NavStatus == ATON_VIRTUAL_OFFPOSITION) ||
      (td->NavStatus == ATON_REAL_OFFPOSITION))
    aton_DrawPen = wxPen(GetGlobalColor(_T ( "URED" )), 2);
  else
    aton_DrawPen = wxPen(GetGlobalColor(_T ( "UBLCK" )), 2);

  bool b_virt = (td->NavStatus == ATON_VIRTUAL) |
                (td->NavStatus == ATON_VIRTUAL_ONPOSITION) |
                (td->NavStatus == ATON_VIRTUAL_OFFPOSITION);

  if (b_virt) aton_DrawPen.SetStyle(wxPENSTYLE_SHORT_DASH);

  aton_WhiteBorderPen =
      wxPen(GetGlobalColor(_T ( "CHWHT" )), aton_DrawPen.GetWidth() + 2);

  // Draw Base Diamond. First with Thick White pen then custom pen io to get a
  // white border around the line.
  wxPoint diamond[5];
  diamond[0] = wxPoint(radius, 0);
  diamond[1] = wxPoint(0, -radius);
  diamond[2] = wxPoint(-radius, 0);
  diamond[3] = wxPoint(0, radius);
  diamond[4] = wxPoint(radius, 0);
  dc.SetPen(aton_WhiteBorderPen);
  dc.DrawLines(5, diamond, x, y);
  dc.SetPen(aton_DrawPen);
  dc.DrawLines(5, diamond, x, y);

  aton_DrawPen = wxPen(GetGlobalColor(_T ( "UBLCK" )),
                       1);  // Change drawing pen to Solid and width 1
  aton_WhiteBorderPen =
      wxPen(GetGlobalColor(_T ( "CHWHT" )), aton_DrawPen.GetWidth() + 2);

  // draw cross inside
  wxPoint cross[5];
  cross[0] = wxPoint(-rad2a, 0);
  cross[1] = wxPoint(rad2a, 0);
  cross[2] = wxPoint(0, 0);
  cross[3] = wxPoint(0, rad2a);
  cross[4] = wxPoint(0, -rad2a);
  dc.SetPen(aton_WhiteBorderPen);
  dc.DrawLines(5, cross, x, y);
  dc.SetPen(aton_DrawPen);
  dc.DrawLines(5, cross, x, y);

  wxPoint TriPointUp[4];  // Declare triangles here for multiple use
  TriPointUp[0] = wxPoint(-rad1a, 0);
  TriPointUp[1] = wxPoint(rad1a, 0);
  TriPointUp[2] = wxPoint(0, -rad1a);
  TriPointUp[3] = wxPoint(-rad1a, 0);

  wxPoint TriPointDown[4];  // Declare triangles here for multiple use
  TriPointDown[0] = wxPoint(-rad1a, -rad1a);
  TriPointDown[1] = wxPoint(rad1a, -rad1a);
  TriPointDown[2] = wxPoint(0, 0);
  TriPointDown[3] = wxPoint(-rad1a, -rad1a);

  wxPoint CircleOpen[16];  // Workaround to draw transparent circles
  CircleOpen[0] = wxPoint(-1, 5);
  CircleOpen[1] = wxPoint(1, 5);
  CircleOpen[2] = wxPoint(3, 4);
  CircleOpen[3] = wxPoint(4, 3);
  CircleOpen[4] = wxPoint(5, 1);
  CircleOpen[5] = wxPoint(5, -1);
  CircleOpen[6] = wxPoint(4, -3);
  CircleOpen[7] = wxPoint(3, -4);
  CircleOpen[8] = wxPoint(1, -5);
  CircleOpen[9] = wxPoint(-1, -5);
  CircleOpen[10] = wxPoint(-3, -4);
  CircleOpen[11] = wxPoint(-4, -3);
  CircleOpen[12] = wxPoint(-5, -1);
  CircleOpen[13] = wxPoint(-4, 3);
  CircleOpen[14] = wxPoint(-3, 4);
  CircleOpen[15] = wxPoint(-1, 5);

  switch (td->ShipType) {
    case 9:
    case 20:  // Card. N
      dc.SetPen(aton_WhiteBorderPen);
      dc.DrawLines(4, TriPointUp, x, y - radius - 1);
      dc.DrawLines(4, TriPointUp, x, y - radius - rad1a - 3);
      dc.SetPen(aton_DrawPen);
      dc.DrawLines(4, TriPointUp, x, y - radius - 1);
      dc.DrawLines(4, TriPointUp, x, y - radius - rad1a - 3);
      break;
    case 10:
    case 21:  // Card E
      dc.SetPen(aton_WhiteBorderPen);
      dc.DrawLines(4, TriPointDown, x, y - radius - 1);
      dc.DrawLines(4, TriPointUp, x, y - radius - rad1a - 3);
      dc.SetPen(aton_DrawPen);
      dc.DrawLines(4, TriPointDown, x, y - radius - 1);
      dc.DrawLines(4, TriPointUp, x, y - radius - rad1a - 3);
      break;
    case 11:
    case 22:  // Card S
      dc.SetPen(aton_WhiteBorderPen);
      dc.DrawLines(4, TriPointDown, x, y - radius - 1);
      dc.DrawLines(4, TriPointDown, x, y - radius - rad1a - 3);
      dc.SetPen(aton_DrawPen);
      dc.DrawLines(4, TriPointDown, x, y - radius - 1);
      dc.DrawLines(4, TriPointDown, x, y - radius - rad1a - 3);
      break;
    case 12:
    case 23:  // Card W
      dc.SetPen(aton_WhiteBorderPen);
      dc.DrawLines(4, TriPointUp, x, y - radius - 1);
      dc.DrawLines(4, TriPointDown, x, y - radius - rad1a - 3);
      dc.SetPen(aton_DrawPen);
      dc.DrawLines(4, TriPointUp, x, y - radius - 1);
      dc.DrawLines(4, TriPointDown, x, y - radius - rad1a - 3);
      break;
    case 13:             // PortHand Beacon IALA-A
    case 24: {           // StarboardHand Beacon IALA-B
      wxPoint aRect[5];  // Square topmark
      aRect[0] = wxPoint(-rad3a, 0);
      aRect[1] = wxPoint(-rad3a, -rad3a - rad3a);
      aRect[2] = wxPoint(rad3a, -rad3a - rad3a);
      aRect[3] = wxPoint(rad3a, 0);
      aRect[4] = wxPoint(-rad3a, 0);
      dc.SetPen(aton_WhiteBorderPen);
      dc.DrawLines(5, aRect, x, y - radius - 1);
      dc.SetPen(aton_DrawPen);
      dc.DrawLines(5, aRect, x, y - radius - 1);
    } break;
    case 14:  // StarboardHand Beacon IALA-A
    case 25:  // PortHand Beacon IALA-B
      dc.SetPen(aton_WhiteBorderPen);
      dc.DrawLines(4, TriPointUp, x, y - radius);
      dc.SetPen(aton_DrawPen);
      dc.DrawLines(4, TriPointUp, x, y - radius);
      break;
    case 17:
    case 28:  // Isolated danger
      dc.SetPen(aton_WhiteBorderPen);
      dc.DrawLines(16, CircleOpen, x, y - radius - 5);
      dc.SetPen(aton_DrawPen);
      dc.DrawLines(16, CircleOpen, x, y - radius - 5);
      dc.SetPen(aton_WhiteBorderPen);
      dc.DrawLines(16, CircleOpen, x, y - radius - 16);
      dc.SetPen(aton_DrawPen);
      dc.DrawLines(16, CircleOpen, x, y - radius - 16);
      break;
    case 18:
    case 29:  // Safe water
      dc.SetPen(aton_WhiteBorderPen);
      dc.DrawLines(16, CircleOpen, x, y - radius - 5);
      dc.SetPen(aton_DrawPen);
      dc.DrawLines(16, CircleOpen, x, y - radius - 5);
      break;
    case 19:
    case 30: {                             // Special Mark
      cross[0] = wxPoint(-rad2a, -rad2a);  // reuse of cross array
      cross[1] = wxPoint(rad2a, rad2a);
      cross[2] = wxPoint(0, 0);
      cross[3] = wxPoint(-rad2a, rad2a);
      cross[4] = wxPoint(rad2a, -rad2a);
      dc.SetPen(aton_WhiteBorderPen);
      dc.DrawLines(5, cross, x, y - radius - rad3a);
      dc.SetPen(aton_DrawPen);
      dc.DrawLines(5, cross, x, y - radius - rad3a);
    } break;
    default:
      break;
  }
  dc.SetPen(pen_save);
}

static void Base_Square(ocpnDC &dc, wxPen pen, int x, int y, int radius) {
  //    Constants?
  int gap2 = 2 * radius / 6;
  int pen_width = pen.GetWidth();

  wxPen pen_save = dc.GetPen();

  dc.SetPen(pen);  // draw square

  dc.DrawLine(x - radius, y - radius, x - radius, y + radius);
  dc.DrawLine(x - radius, y + radius, x + radius, y + radius);
  dc.DrawLine(x + radius, y + radius, x + radius, y - radius);
  dc.DrawLine(x + radius, y - radius, x - radius, y - radius);

  if (pen_width > 1) {
    pen_width -= 1;
    pen.SetWidth(pen_width);
  }  // draw cross inside

  dc.DrawLine(x - gap2, y, x + gap2, y);
  dc.DrawLine(x, y - gap2, x, y + gap2);

  dc.SetPen(pen_save);
}

static void SART_Render(ocpnDC &dc, wxPen pen, int x, int y, int radius) {
  //    Constants
  int gap = (radius * 12) / 10;
  int pen_width = pen.GetWidth();

  wxPen pen_save = dc.GetPen();

  dc.SetPen(pen);

  wxBrush brush_save = dc.GetBrush();
  wxBrush *ppBrush = wxTheBrushList->FindOrCreateBrush(
      wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT);
  dc.SetBrush(*ppBrush);

  dc.DrawCircle(x, y, radius);

  if (pen_width > 1) {
    pen_width -= 1;
    pen.SetWidth(pen_width);
  }  // draw cross inside

  dc.DrawLine(x - gap, y - gap, x + gap, y + gap);
  dc.DrawLine(x - gap, y + gap, x + gap, y - gap);

  dc.SetBrush(brush_save);
  dc.SetPen(pen_save);
}

// spherical coordinates is sufficient for visually plotting with relatively
// small distances and about 6x faster than ll_gc_ll
static void spherical_ll_gc_ll(float lat, float lon, float brg, float dist,
                               float *dlat, float *dlon) {
  float angr = brg / 180 * M_PI;
  float latr = lat * M_PI / 180;
  float D = dist / 3443;  // earth radius in nm
  float sD = sinf(D), cD = cosf(D);
  float sy = sinf(latr), cy = cosf(latr);
  float sa = sinf(angr), ca = cosf(angr);

  *dlon = lon + asinf(sa * sD / cy) * 180 / M_PI;
  *dlat = asinf(sy * cD + cy * sD * ca) * 180 / M_PI;
}

//  Global static AIS target rendering metrics
float AIS_scale_factor;
float AIS_nominal_target_size_mm;
float AIS_nominal_icon_size_pixels;
float AIS_pix_factor;
float AIS_user_scale_factor;
double AIS_nominal_line_width_pix;

float AIS_width_interceptbar_base;
float AIS_width_interceptbar_top;
float AIS_intercept_bar_circle_diameter;
float AIS_width_interceptline;
float AIS_width_cogpredictor_base;
float AIS_width_cogpredictor_line;
float AIS_width_target_outline;

static void AISSetMetrics() {
  AIS_scale_factor = 1.0;

  //  Set the onscreen size of the symbol
  //  Compensate for various display resolutions
  //  Develop empirically, making a "diamond ATON" symbol about 4 mm square

  // By experience, it is found that specifying target size in pixels, then
  // bounding rendered size for high or lo resolution displays, gives the best
  // compromise.

  AIS_nominal_target_size_mm = 30.0 / g_Platform->GetDisplayDPmm();
  // nominal_target_size_mm = gFrame->GetPrimaryCanvas()->GetDisplaySizeMM()
  // / 60.0;

  AIS_nominal_target_size_mm = wxMin(AIS_nominal_target_size_mm, 10.0);
  AIS_nominal_target_size_mm = wxMax(AIS_nominal_target_size_mm, 6.0);

  AIS_nominal_icon_size_pixels =
      wxMax(4.0, floor(g_Platform->GetDisplayDPmm() *
                       AIS_nominal_target_size_mm));  // nominal size, but not
                                                      // less than 4 pixel
  AIS_pix_factor = AIS_nominal_icon_size_pixels /
                   30.0;  // generic A/B icons are 30 units in size

  AIS_scale_factor *= AIS_pix_factor;

  AIS_user_scale_factor = g_ShipScaleFactorExp;
  if (g_ShipScaleFactorExp > 1.0)
    AIS_user_scale_factor = (log(g_ShipScaleFactorExp) + 1.0) *
                            1.2;  // soften the scale factor a bit

  AIS_scale_factor *= AIS_user_scale_factor;

  //  Establish some graphic element line widths dependent on the platform
  //  display resolution
  AIS_nominal_line_width_pix =
      wxMax(1.5, floor(g_Platform->GetDisplayDPmm() /
                       5.0));  // 0.4 mm nominal, but not less than 1 pixel

  AIS_width_interceptbar_base = 3 * AIS_nominal_line_width_pix;
  AIS_width_interceptbar_top = 1.5 * AIS_nominal_line_width_pix;
  AIS_intercept_bar_circle_diameter = 4 * AIS_nominal_line_width_pix;
  AIS_width_interceptline = 2 * AIS_nominal_line_width_pix;
  AIS_width_cogpredictor_base = 3 * AIS_nominal_line_width_pix;
  AIS_width_cogpredictor_line = 1.5 * AIS_nominal_line_width_pix;
  AIS_width_target_outline = 1.2 * AIS_nominal_line_width_pix;
}

static void AISDrawTarget(AIS_Target_Data *td, ocpnDC &dc, ViewPort &vp,
                          ChartCanvas *cp) {
  //      Target data must be valid
  if (NULL == td) return;

  //    Target is lost due to position report time-out, but still in Target List
  if (td->b_lost) return;

  //      Skip anchored/moored (interpreted as low speed) targets if requested
  //      unless the target is NUC or AtoN, in which case it is always
  //      displayed.
  if ((g_bHideMoored) && (td->SOG <= g_ShowMoored_Kts) &&
      (td->NavStatus != NOT_UNDER_COMMAND) &&
      ((td->Class == AIS_CLASS_A) || (td->Class == AIS_CLASS_B)))
    return;

  //      Target data position must have been valid once
  if (!td->b_positionOnceValid) return;

  // And we never draw ownship
  if (td->b_OwnShip) return;

  //    If target's speed is unavailable, use zero for further calculations
  float target_sog = td->SOG;
  if ((td->SOG > 102.2) && !td->b_SarAircraftPosnReport) target_sog = 0.;

  int drawit = 0;
  wxPoint TargetPoint, PredPoint;

  //   Always draw alert targets, even if they are off the screen
  if (td->n_alert_state == AIS_ALERT_SET)
    drawit++;
  else
      //    Is target in Vpoint?
      if (vp.GetBBox().Contains(td->Lat, td->Lon))
    drawit++;  // yep
  else
      //  If AIS tracks are shown, is the first point of the track on-screen?
      if (1 /*g_bAISShowTracks*/ && td->b_show_track) {
    wxAISTargetTrackListNode *node = td->m_ptrack->GetFirst();
    if (node) {
      AISTargetTrackPoint *ptrack_point = node->GetData();
      if (vp.GetBBox().Contains(ptrack_point->m_lat, ptrack_point->m_lon))
        drawit++;
    }
  }

  //    Calculate AIS target Position Predictor, using global static variable
  //    for length of vector

  float pred_lat, pred_lon;
  spherical_ll_gc_ll(td->Lat, td->Lon, td->COG,
                     target_sog * g_ShowCOG_Mins / 60., &pred_lat, &pred_lon);

  //    Is predicted point in the VPoint?
  if (vp.GetBBox().Contains(pred_lat, pred_lon))
    drawit++;  // yep
  else {
    LLBBox box;
    box.SetFromSegment(td->Lat, td->Lon, pred_lat, pred_lon);
    // And one more test to catch the case where target COG line crosses the
    // screen, but the target itself and its pred point are both off-screen
    if (!vp.GetBBox().IntersectOut(box)) drawit++;
  }

  //    Do the draw if conditions indicate
  if (!drawit) return;

  GetCanvasPointPix(vp, cp, td->Lat, td->Lon, &TargetPoint);
  GetCanvasPointPix(vp, cp, pred_lat, pred_lon, &PredPoint);

  bool b_hdgValid = true;

  float theta = (float)-PI / 2.;
  //    If the target reported a valid HDG, then use it for icon
  if ((int)(td->HDG) != 511) {
    theta = ((td->HDG - 90) * PI / 180.) + vp.rotation;
  } else {
    b_hdgValid = false;  // tentative judgement

    if (!g_bInlandEcdis) {
      // question: why can we not compute similar to above using COG instead of
      // HDG?
      //  Calculate the relative angle for this chart orientation
      //    Use a 100 pixel vector to calculate angle
      float angle_distance_nm = (100. / vp.view_scale_ppm) / 1852.;
      float angle_lat, angle_lon;
      spherical_ll_gc_ll(td->Lat, td->Lon, td->COG, angle_distance_nm,
                         &angle_lat, &angle_lon);

      wxPoint AnglePoint;
      GetCanvasPointPix(vp, cp, angle_lat, angle_lon, &AnglePoint);

      if (abs(AnglePoint.x - TargetPoint.x) > 0) {
        if (target_sog > g_ShowMoored_Kts) {
          theta = atan2f((double)(AnglePoint.y - TargetPoint.y),
                         (double)(AnglePoint.x - TargetPoint.x));
          b_hdgValid = true;
        } else
          theta = (float)-PI / 2.;
      } else {
        if (AnglePoint.y > TargetPoint.y)
          theta = (float)PI / 2.;  // valid COG 180
        else {
          theta = (float)-PI /
                  2.;  //  valid COG 000 or speed is too low to resolve course
          if (td->SOG >= g_ShowMoored_Kts)  //  valid COG 000 or speed is too
                                            //  low to resolve course
            b_hdgValid = true;
        }
      }
    }
  }

  // only need to compute this once;
  float sin_theta = sinf(theta), cos_theta = cosf(theta);

  wxDash dash_long[2];
  dash_long[0] = (int)(1.0 * gFrame->GetPrimaryCanvas()
                                 ->GetPixPerMM());  // Long dash  <---------+
  dash_long[1] =
      (int)(0.5 * gFrame->GetPrimaryCanvas()->GetPixPerMM());  // Short gap |

  int targetscale = 100;
  int idxCC = 0;
  if (cp != NULL) {
    idxCC = cp->m_canvasIndex;

    if (idxCC > AIS_TARGETDATA_MAX_CANVAS - 1)
      return;  // If more then n canvasses do not draw AIS anymore as we are
               // running out of array index
    if (cp->GetAttenAIS()) {
      if (td->NavStatus <= 15) {  // NavStatus > 15 is AtoN, and we don want
                                  // AtoN being counted for attenuation
        // with one tick per second targets can slink from 100 to 50 in abt 25
        // seconds
        if (td->importance < AISImportanceSwitchPoint)
          targetscale = td->last_scale[idxCC] - 2;
        // growing from 50 till 100% goes faster in 10 seconds
        if (td->importance > AISImportanceSwitchPoint)
          targetscale = td->last_scale[idxCC] + 5;
        if (targetscale > 100) targetscale = 100;
        if (targetscale < 50) targetscale = 50;
        td->last_scale[idxCC] = targetscale;
      }
    }
  }

  //  Draw the icon rotated to the COG
  wxPoint ais_real_size[6];
  bool bcan_draw_size = true;
  if (g_bDrawAISSize) {
    if (td->DimA + td->DimB == 0 || td->DimC + td->DimD == 0) {
      bcan_draw_size = false;
    } else {
      double ref_lat, ref_lon;
      ll_gc_ll(td->Lat, td->Lon, 0, 100. / 1852., &ref_lat, &ref_lon);
      wxPoint2DDouble b_point = vp.GetDoublePixFromLL(td->Lat, td->Lon);
      wxPoint2DDouble r_point = vp.GetDoublePixFromLL(ref_lat, ref_lon);
      double ppm = r_point.GetDistance(b_point) / 100.;
      double offwid = (td->DimC + td->DimD) * ppm * 0.25;
      double offlen = (td->DimA + td->DimB) * ppm * 0.15;
      ais_real_size[0].x = -td->DimD * ppm;
      ais_real_size[0].y = -td->DimB * ppm;
      ais_real_size[1].x = -td->DimD * ppm;
      ais_real_size[1].y = td->DimA * ppm - offlen;
      ais_real_size[2].x = -td->DimD * ppm + offwid;
      ais_real_size[2].y = td->DimA * ppm;
      ais_real_size[3].x = td->DimC * ppm - offwid;
      ais_real_size[3].y = td->DimA * ppm;
      ais_real_size[4].x = td->DimC * ppm;
      ais_real_size[4].y = td->DimA * ppm - offlen;
      ais_real_size[5].x = td->DimC * ppm;
      ais_real_size[5].y = -td->DimB * ppm;

      if (ais_real_size[4].x - ais_real_size[0].x < 16 ||
          ais_real_size[2].y - ais_real_size[0].y < 30)
        bcan_draw_size = false;  // drawing too small does not make sense
      else {
        bcan_draw_size = true;
        transrot_pts(6, ais_real_size, sin_theta, cos_theta);
      }
    }
  }

  wxPoint *iconPoints;
  int nPoints;
  wxPoint ais_quad_icon[4] = {wxPoint(-8, -6), wxPoint(0, 24), wxPoint(8, -6),
                              wxPoint(0, -6)};
  wxPoint ais_octo_icon[8] = {wxPoint(4, 8),  wxPoint(8, 4),   wxPoint(8, -4),
                              wxPoint(4, -8), wxPoint(-4, -8), wxPoint(-8, -4),
                              wxPoint(-8, 4), wxPoint(-4, 8)};

  if (!g_bInlandEcdis) {
    // to speed up we only calculate scale when not max or minimal
    if (targetscale == 50) {
      ais_quad_icon[0] = wxPoint(-4, -3);
      ais_quad_icon[1] = wxPoint(0, 12);
      ais_quad_icon[2] = wxPoint(4, -3);
      ais_quad_icon[3] = wxPoint(0, -3);
    } else if (targetscale != 100) {
      ais_quad_icon[0] =
          wxPoint((int)-8 * targetscale / 100, (int)-6 * targetscale / 100);
      ais_quad_icon[1] = wxPoint(0, (int)24 * targetscale / 100);
      ais_quad_icon[2] =
          wxPoint((int)8 * targetscale / 100, (int)-6 * targetscale / 100);
      ais_quad_icon[3] = wxPoint(0, (int)-6 * targetscale / 100);
    }

    //   If this is an AIS Class B target, so symbolize it differently
    if (td->Class == AIS_CLASS_B) ais_quad_icon[3].y = 0;

    if ((td->Class == AIS_GPSG_BUDDY) || (td->b_isFollower)) {
      ais_quad_icon[0] = wxPoint(-5, -12);
      ais_quad_icon[1] = wxPoint(-3, 12);
      ais_quad_icon[2] = wxPoint(3, 12);
      ais_quad_icon[3] = wxPoint(5, -12);
    } else if (td->Class == AIS_DSC) {
      ais_quad_icon[0].y = 0;
      ais_quad_icon[1].y = 8;
      ais_quad_icon[2].y = 0;
      ais_quad_icon[3].y = -8;
    } else if (td->Class == AIS_APRS) {
      ais_quad_icon[0] = wxPoint(-8, -8);
      ais_quad_icon[1] = wxPoint(-8, 8);
      ais_quad_icon[2] = wxPoint(8, 8);
      ais_quad_icon[3] = wxPoint(8, -8);
    }

    transrot_pts(4, ais_quad_icon, sin_theta, cos_theta);

    nPoints = 4;
    iconPoints = ais_quad_icon;

  } else {  // iENC
    if (b_hdgValid) {
      transrot_pts(4, ais_quad_icon, sin_theta, cos_theta);
      nPoints = 4;
      iconPoints = ais_quad_icon;
    } else {
      nPoints = 8;
      iconPoints = ais_octo_icon;
    }
  }

  wxColour UBLCK = GetGlobalColor(_T ( "UBLCK" ));
  dc.SetPen(wxPen(UBLCK));

  // Default color is green
  wxColour UINFG = GetGlobalColor(_T ( "UINFG" ));
  wxBrush target_brush = wxBrush(UINFG);

  // Euro Inland targets render slightly differently, unless in InlandENC mode
  if (td->b_isEuroInland && !g_bInlandEcdis)
    target_brush = wxBrush(GetGlobalColor(_T ( "TEAL1" )));

  // Target name comes from cache
  if (td->b_nameFromCache)
    target_brush = wxBrush(GetGlobalColor(_T ( "GREEN5" )));

  // and....
  wxColour URED = GetGlobalColor(_T ( "URED" ));
  if (!td->b_nameValid) target_brush = wxBrush(GetGlobalColor(_T ( "CHYLW" )));
  if ((td->Class == AIS_DSC) && (td->ShipType == 12))  // distress
    target_brush = wxBrush(URED);
  if (td->b_SarAircraftPosnReport) target_brush = wxBrush(UINFG);

  if ((td->n_alert_state == AIS_ALERT_SET) && (td->bCPA_Valid))
    target_brush = wxBrush(URED);

  if ((td->n_alert_state == AIS_ALERT_NO_DIALOG_SET) && (td->bCPA_Valid))
    target_brush = wxBrush(URED);

  if (td->b_positionDoubtful)
    target_brush = wxBrush(GetGlobalColor(_T ( "UINFF" )));

  wxPen target_outline_pen(UBLCK, AIS_width_target_outline);

  //    Check for alarms here, maintained by AIS class timer tick
  if (((td->n_alert_state == AIS_ALERT_SET) && (td->bCPA_Valid)) ||
      (td->b_show_AIS_CPA && (td->bCPA_Valid))) {
    //  Calculate the point of CPA for target
    double tcpa_lat, tcpa_lon;
    ll_gc_ll(td->Lat, td->Lon, td->COG, target_sog * td->TCPA / 60., &tcpa_lat,
             &tcpa_lon);
    wxPoint tCPAPoint;
    wxPoint TPoint = TargetPoint;
    GetCanvasPointPix(vp, cp, tcpa_lat, tcpa_lon, &tCPAPoint);

    //  Draw the intercept line from target
    ClipResult res = cohen_sutherland_line_clip_i(
        &TPoint.x, &TPoint.y, &tCPAPoint.x, &tCPAPoint.y, 0, vp.pix_width, 0,
        vp.pix_height);

    if (res != Invisible) {
      wxPen ppPen2(URED, AIS_width_cogpredictor_line, wxPENSTYLE_USER_DASH);
      ppPen2.SetDashes(2, dash_long);
      dc.SetPen(ppPen2);

      dc.StrokeLine(TPoint.x, TPoint.y, tCPAPoint.x, tCPAPoint.y);
    }

    //  Calculate the point of CPA for ownship
    double ocpa_lat, ocpa_lon;

    //  Detect and handle the case where ownship COG is undefined....
    if (std::isnan(gCog) || std::isnan(gSog)) {
      ocpa_lat = gLat;
      ocpa_lon = gLon;
    } else {
      ll_gc_ll(gLat, gLon, gCog, gSog * td->TCPA / 60., &ocpa_lat, &ocpa_lon);
    }

    wxPoint oCPAPoint;

    GetCanvasPointPix(vp, cp, ocpa_lat, ocpa_lon, &oCPAPoint);
    GetCanvasPointPix(vp, cp, tcpa_lat, tcpa_lon, &tCPAPoint);

    //        Save a copy of these unclipped points
    wxPoint oCPAPoint_unclipped = oCPAPoint;
    wxPoint tCPAPoint_unclipped = tCPAPoint;

    //  Draw a line from target CPA point to ownship CPA point
    ClipResult ores = cohen_sutherland_line_clip_i(
        &tCPAPoint.x, &tCPAPoint.y, &oCPAPoint.x, &oCPAPoint.y, 0, vp.pix_width,
        0, vp.pix_height);

    if (ores != Invisible) {
      wxColour yellow = GetGlobalColor(_T ( "YELO1" ));
      dc.SetPen(wxPen(yellow, AIS_width_interceptbar_base));
      dc.StrokeLine(tCPAPoint.x, tCPAPoint.y, oCPAPoint.x, oCPAPoint.y);

      wxPen ppPen2(URED, AIS_width_interceptbar_top, wxPENSTYLE_USER_DASH);
      ppPen2.SetDashes(2, dash_long);
      dc.SetPen(ppPen2);
      dc.StrokeLine(tCPAPoint.x, tCPAPoint.y, oCPAPoint.x, oCPAPoint.y);

      //        Draw little circles at the ends of the CPA alert line
      wxBrush br(GetGlobalColor(_T ( "BLUE3" )));
      dc.SetBrush(br);
      dc.SetPen(wxPen(UBLCK, AIS_width_target_outline));

      //  Using the true ends, not the clipped ends
      dc.StrokeCircle(tCPAPoint_unclipped.x, tCPAPoint_unclipped.y,
                      AIS_intercept_bar_circle_diameter);
      dc.StrokeCircle(oCPAPoint_unclipped.x, oCPAPoint_unclipped.y,
                      AIS_intercept_bar_circle_diameter);
    }

    // Draw the intercept line from ownship
    wxPoint oShipPoint;
    GetCanvasPointPix(vp, cp, gLat, gLon, &oShipPoint);
    oCPAPoint = oCPAPoint_unclipped;  // recover the unclipped point

    ClipResult ownres = cohen_sutherland_line_clip_i(
        &oShipPoint.x, &oShipPoint.y, &oCPAPoint.x, &oCPAPoint.y, 0,
        vp.pix_width, 0, vp.pix_height);

    if (ownres != Invisible) {
      wxPen ppPen2(URED, AIS_width_interceptline, wxPENSTYLE_USER_DASH);
      ppPen2.SetDashes(2, dash_long);
      dc.SetPen(ppPen2);

      dc.StrokeLine(oShipPoint.x, oShipPoint.y, oCPAPoint.x, oCPAPoint.y);
    }  // TR : till here

    dc.SetPen(wxPen(UBLCK));
    dc.SetBrush(wxBrush(URED));
  }

  //  Highlight the AIS target symbol if an alert dialog is currently open for
  //  it
  if (cp != NULL) {
    if (g_pais_alert_dialog_active && g_pais_alert_dialog_active->IsShown() &&
        cp) {
      if (g_pais_alert_dialog_active->Get_Dialog_MMSI() == td->MMSI)
        cp->JaggyCircle(dc, wxPen(URED, 2), TargetPoint.x, TargetPoint.y, 100);
    }
  }

  //  Highlight the AIS target symbol if a query dialog is currently open for it
  if (g_pais_query_dialog_active && g_pais_query_dialog_active->IsShown()) {
    if (g_pais_query_dialog_active->GetMMSI() == td->MMSI)
      TargetFrame(dc, wxPen(UBLCK, 2), TargetPoint.x, TargetPoint.y, 25);
  }

  //       Render the COG line if the speed is greater than moored speed defined
  //       by ais options dialog
  if ((g_bShowCOG) && (target_sog > g_ShowMoored_Kts) && td->b_active) {
    int pixx = TargetPoint.x;
    int pixy = TargetPoint.y;
    int pixx1 = PredPoint.x;
    int pixy1 = PredPoint.y;

    //  Don't draw the COG line  and predictor point if zoomed far out.... or if
    //  target lost/inactive
    float l = sqrtf(powf((float)(PredPoint.x - TargetPoint.x), 2) +
                    powf((float)(PredPoint.y - TargetPoint.y), 2));

    if (l > 24) {
      ClipResult res = cohen_sutherland_line_clip_i(
          &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);

      if (res != Invisible) {
        //    Draw a wider coloured line
        if (targetscale >= 75) {
          wxPen wide_pen(target_brush.GetColour(), AIS_width_cogpredictor_base);
          dc.SetPen(wide_pen);
          dc.StrokeLine(pixx, pixy, pixx1, pixy1);
        }

        if (AIS_width_cogpredictor_base > 1) {
          //    Draw narrow black line
          wxPen narrow_pen(UBLCK, AIS_width_cogpredictor_line);
          if (targetscale < 75) {
            narrow_pen.SetWidth(1);
            narrow_pen.SetStyle(wxPENSTYLE_DOT);
          }
          dc.SetPen(narrow_pen);
          dc.StrokeLine(pixx, pixy, pixx1, pixy1);
        }

        if (dc.GetDC()) {
          dc.SetBrush(target_brush);
          if (targetscale >= 75)
            dc.StrokeCircle(PredPoint.x, PredPoint.y, 5);
          else
            dc.StrokeCircle(PredPoint.x, PredPoint.y, 2);
        } else {
#ifdef ocpnUSE_GL

#ifndef USE_ANDROID_GLES2
          glPushMatrix();
          glTranslated(PredPoint.x, PredPoint.y, 0);
          glScalef(AIS_scale_factor, AIS_scale_factor, AIS_scale_factor);
          // draw circle
          float points[] = {0.0f,       5.0f,       2.5f,  4.330127f, 4.330127f,
                            2.5f,       5.0f,       0,     4.330127f, -2.5f,
                            2.5f,       -4.330127f, 0,     -5.1f,     -2.5f,
                            -4.330127f, -4.330127f, -2.5f, -5.0f,     0,
                            -4.330127f, 2.5f,       -2.5f, 4.330127f, 0,
                            5.0f};
          if (targetscale <= 75) {
            for (unsigned int i = 0; i < (sizeof points) / (sizeof *points);
                 i++)
              points[i] = points[i] / 2;
          }

          wxColour c = target_brush.GetColour();
          glColor3ub(c.Red(), c.Green(), c.Blue());

          glBegin(GL_TRIANGLE_FAN);
          for (unsigned int i = 0; i < (sizeof points) / (sizeof *points);
               i += 2)
            glVertex2i(points[i], points[i + 1]);
          glEnd();

          glColor3ub(0, 0, 0);
          glLineWidth(AIS_width_target_outline);
          glBegin(GL_LINE_LOOP);
          for (unsigned int i = 0; i < (sizeof points) / (sizeof *points);
               i += 2)
            glVertex2i(points[i], points[i + 1]);
          glEnd();
          glPopMatrix();
#else
          double nominal_circle_size_pixels = wxMax(
              14.0,
              floor(g_Platform->GetDisplayDPmm() *
                    1));  // 1.0 mm nominal diameter, but not less than 4 pixel
          dc.SetBrush(target_brush);
          dc.StrokeCircle(PredPoint.x, PredPoint.y,
                          nominal_circle_size_pixels / 2);

#endif
#endif
        }
      }

      //      Draw RateOfTurn Vector
      if ((td->ROTAIS != 0) && (td->ROTAIS != -128) && (!g_bShowScaled)) {
        float cog_angle = td->COG * PI / 180.;

        float theta2 = theta;  // ownship drawn angle
        if (td->SOG >= g_ShowMoored_Kts)
          theta2 = cog_angle - (PI / 2);  // actual cog angle

        float nv = 10;
        if (td->ROTAIS > 0)
          theta2 += (float)PI / 2;
        else
          theta2 -= (float)PI / 2;

        int xrot = (int)round(pixx1 + (nv * cosf(theta2)));
        int yrot = (int)round(pixy1 + (nv * sinf(theta2)));
        dc.StrokeLine(pixx1, pixy1, xrot, yrot);
      }
    }
  }

  //        Actually Draw the target
  if (td->Class == AIS_ARPA) {
    wxPen target_pen(UBLCK, 2);

    dc.SetPen(target_pen);
    dc.SetBrush(target_brush);

    dc.StrokeCircle(TargetPoint.x, TargetPoint.y, 9);
    dc.StrokeCircle(TargetPoint.x, TargetPoint.y, 1);
    //        Draw the inactive cross-out line
    if (!td->b_active) {
      dc.SetPen(wxPen(UBLCK, 2));
      dc.StrokeLine(TargetPoint.x - 14, TargetPoint.y, TargetPoint.x + 14,
                    TargetPoint.y);
      dc.SetPen(wxPen(UBLCK, 1));
    }
  } else if (td->Class == AIS_ATON) {  // Aid to Navigation
    AtoN_Diamond(dc, TargetPoint.x, TargetPoint.y, 12, td);
  } else if (td->Class == AIS_BASE) {  // Base Station
    Base_Square(dc, wxPen(UBLCK, 2), TargetPoint.x, TargetPoint.y, 8);
  } else if (td->Class == AIS_SART) {  // SART Target
    if (td->NavStatus == 14)           // active
      SART_Render(dc, wxPen(URED, 2), TargetPoint.x, TargetPoint.y, 8);
    else
      SART_Render(dc, wxPen(GetGlobalColor(_T ( "UGREN" )), 2), TargetPoint.x,
                  TargetPoint.y, 8);

  } else if (td->b_SarAircraftPosnReport) {
    int airtype = (td->MMSI % 1000) / 100;  // xxxyyy5zz >> helicopter
    int ar = airtype == 5 ? 15 : 9;         // array size
    wxPoint SarIcon[15];
    wxPoint SarRot[15];

    if (airtype == 5) {
      SarIcon[0] = wxPoint(0, 9);
      SarIcon[1] = wxPoint(1, 1);
      SarIcon[2] = wxPoint(2, 1);
      SarIcon[3] = wxPoint(9, 8);
      SarIcon[4] = wxPoint(9, 7);
      SarIcon[5] = wxPoint(3, 0);
      SarIcon[6] = wxPoint(3, -5);
      SarIcon[7] = wxPoint(9, -12);
      SarIcon[8] = wxPoint(9, -13);
      SarIcon[9] = wxPoint(2, -5);
      SarIcon[10] = wxPoint(1, -15);
      SarIcon[11] = wxPoint(3, -16);
      SarIcon[12] = wxPoint(4, -18);
      SarIcon[13] = wxPoint(1, -18);
      SarIcon[14] = wxPoint(0, -19);
    } else {
      SarIcon[0] = wxPoint(0, 12);
      SarIcon[1] = wxPoint(4, 2);
      SarIcon[2] = wxPoint(16, -2);
      SarIcon[3] = wxPoint(16, -8);
      SarIcon[4] = wxPoint(4, -8);
      SarIcon[5] = wxPoint(3, -16);
      SarIcon[6] = wxPoint(10, -18);
      SarIcon[7] = wxPoint(10, -22);
      SarIcon[8] = wxPoint(0, -22);
    }

    // Draw icon as two halves

    //  First half

    for (int i = 0; i < ar; i++) SarRot[i] = SarIcon[i];
    transrot_pts(ar, SarRot, sin_theta, cos_theta);

    wxPen tri_pen(target_brush.GetColour(), 1);
    dc.SetPen(tri_pen);
    dc.SetBrush(target_brush);

    int mappings[7][3] = {{0, 1, 4}, {1, 2, 3}, {1, 3, 4}, {0, 4, 5},
                          {0, 5, 8}, {5, 6, 7}, {5, 7, 8}};
    for (int i = 0; i < 7; i++) {
      wxPoint ais_tri_icon[3];
      for (int j = 0; j < 3; j++) ais_tri_icon[j] = SarRot[mappings[i][j]];
      dc.StrokePolygon(3, ais_tri_icon, TargetPoint.x, TargetPoint.y);
    }

    dc.SetPen(target_outline_pen);
    dc.SetBrush(wxBrush(UBLCK, wxBRUSHSTYLE_TRANSPARENT));
    dc.StrokePolygon(ar, SarRot, TargetPoint.x, TargetPoint.y);

    // second half

    for (int i = 0; i < ar; i++)
      SarRot[i] =
          wxPoint(-SarIcon[i].x, SarIcon[i].y);  // mirror the icon (x -> -x)

    transrot_pts(ar, SarRot, sin_theta, cos_theta);

    dc.SetPen(tri_pen);
    dc.SetBrush(target_brush);

    for (int i = 0; i < 7; i++) {
      wxPoint ais_tri_icon[3];
      for (int j = 0; j < 3; j++) ais_tri_icon[j] = SarRot[mappings[i][j]];
      dc.StrokePolygon(3, ais_tri_icon, TargetPoint.x, TargetPoint.y);
    }

    dc.SetPen(target_outline_pen);
    dc.SetBrush(wxBrush(UBLCK, wxBRUSHSTYLE_TRANSPARENT));
    dc.StrokePolygon(ar, SarRot, TargetPoint.x, TargetPoint.y);

    //        Draw the inactive cross-out line
    if (!td->b_active) {
      dc.SetPen(wxPen(UBLCK, 3));
      dc.StrokeLine(TargetPoint.x - 16, TargetPoint.y, TargetPoint.x + 16,
                    TargetPoint.y);
    }

  } else {  // ship class A or B or a Buddy or DSC
    wxPen target_pen(UBLCK, 1);
    dc.SetPen(target_pen);

    wxPoint Point = TargetPoint;
    if (g_bDrawAISRealtime &&
        (td->Class == AIS_CLASS_A || td->Class == AIS_CLASS_B) &&
        td->SOG > g_AIS_RealtPred_Kts && td->SOG < 102.2) {
      wxDateTime now = wxDateTime::Now();
      now.MakeGMT();
      int target_age = now.GetTicks() - td->PositionReportTicks;

      float lat, lon;
      spherical_ll_gc_ll(td->Lat, td->Lon, td->COG,
                         td->SOG * target_age / 3600.0, &lat, &lon);

      GetCanvasPointPix(vp, cp, lat, lon, &Point);

      wxBrush realtime_brush = wxBrush(GetGlobalColor("GREY1"));
      dc.SetBrush(realtime_brush);
      dc.StrokePolygon(nPoints, iconPoints, Point.x, Point.y, AIS_scale_factor);
    }
    dc.SetBrush(target_brush);

    if (dc.GetDC()) {
      dc.StrokePolygon(nPoints, iconPoints, TargetPoint.x, TargetPoint.y,
                       AIS_scale_factor);
    } else {
#ifdef ocpnUSE_GL
#ifndef USE_ANDROID_GLES2
      wxColour c = target_brush.GetColour();
      glColor3ub(c.Red(), c.Green(), c.Blue());

      glPushMatrix();
      glTranslated(TargetPoint.x, TargetPoint.y, 0);
      glScalef(AIS_scale_factor, AIS_scale_factor, AIS_scale_factor);

      glBegin(GL_TRIANGLE_FAN);

      if (nPoints == 4) {
        glVertex2i(ais_quad_icon[3].x, ais_quad_icon[3].y);
        glVertex2i(ais_quad_icon[0].x, ais_quad_icon[0].y);
        glVertex2i(ais_quad_icon[1].x, ais_quad_icon[1].y);
        glVertex2i(ais_quad_icon[2].x, ais_quad_icon[2].y);
      } else {
        for (int i = 0; i < 8; i++) {
          glVertex2i(iconPoints[i].x, iconPoints[i].y);
        }
      }

      glEnd();
#else
      dc.SetPen(target_outline_pen);
      dc.DrawPolygon(nPoints, iconPoints, TargetPoint.x, TargetPoint.y,
                     AIS_scale_factor);
#endif

      // Draw target outline, if not already done
      // Depending on platform  (wx) capabilities, draw the nicest lines
      // possible
#if wxUSE_GRAPHICS_CONTEXT
      glPopMatrix();

      dc.SetPen(target_outline_pen);
      dc.SetBrush(wxBrush(UBLCK, wxBRUSHSTYLE_TRANSPARENT));
      dc.StrokePolygon(nPoints, iconPoints, TargetPoint.x, TargetPoint.y,
                       AIS_scale_factor);
#else
      glLineWidth(AIS_width_target_outline);
#ifndef USE_ANDROID_GLES2
      glColor3ub(UBLCK.Red(), UBLCK.Green(), UBLCK.Blue());

      glBegin(GL_LINE_LOOP);
      for (int i = 0; i < nPoints; i++)
        glVertex2i(iconPoints[i].x, iconPoints[i].y);
      glEnd();
      glPopMatrix();

#endif

#endif

#endif
    }
    // Draw stroke "inverted v" for GPS Follower
    if (td->b_isFollower) {
      wxPoint ais_follow_stroke[3];
      ais_follow_stroke[0] = wxPoint(-3, -20);
      ais_follow_stroke[1] = wxPoint(0, 0);
      ais_follow_stroke[2] = wxPoint(3, -20);

      transrot_pts(3, ais_follow_stroke, sin_theta, cos_theta);

      if (dc.GetDC()) {
        dc.SetPen(wxPen(UBLCK, 2));
        dc.StrokeLine(ais_follow_stroke[0].x + TargetPoint.x,
                      ais_follow_stroke[0].y + TargetPoint.y,
                      ais_follow_stroke[1].x + TargetPoint.x,
                      ais_follow_stroke[1].y + TargetPoint.y);
        dc.StrokeLine(ais_follow_stroke[1].x + TargetPoint.x,
                      ais_follow_stroke[1].y + TargetPoint.y,
                      ais_follow_stroke[2].x + TargetPoint.x,
                      ais_follow_stroke[2].y + TargetPoint.y);
      } else {
#ifdef ocpnUSE_GL

        glPushMatrix();
        glTranslated(TargetPoint.x, TargetPoint.y, 0);
        glScalef(AIS_scale_factor, AIS_scale_factor, AIS_scale_factor);

        glLineWidth(AIS_width_target_outline);
#ifndef USE_ANDROID_GLES2
        glColor3ub(UBLCK.Red(), UBLCK.Green(), UBLCK.Blue());

        glBegin(GL_LINE_STRIP);
        for (int i = 0; i < 3; i++)
          glVertex2i(ais_follow_stroke[i].x, ais_follow_stroke[i].y);
        glEnd();

        glPopMatrix();
#endif

#endif
      }
    }

    if (g_bDrawAISSize && bcan_draw_size) {
      dc.SetPen(target_outline_pen);
      dc.SetBrush(wxBrush(UBLCK, wxBRUSHSTYLE_TRANSPARENT));
      if (!g_bInlandEcdis) {
        dc.StrokePolygon(6, ais_real_size, TargetPoint.x, TargetPoint.y, 1.0);
      } else {
        if (b_hdgValid) {
          dc.StrokePolygon(6, ais_real_size, TargetPoint.x, TargetPoint.y, 1.0);
        }
      }
    }

    dc.SetBrush(wxBrush(GetGlobalColor(_T ( "SHIPS" ))));
    int navstatus = td->NavStatus;

    // HSC usually have correct ShipType but navstatus == 0...
    if (((td->ShipType >= 40) && (td->ShipType < 50)) &&
        navstatus == UNDERWAY_USING_ENGINE)
      navstatus = HSC;

    if (targetscale > 90) {
      switch (navstatus) {
        case MOORED:
        case AT_ANCHOR: {
          dc.StrokeCircle(TargetPoint.x, TargetPoint.y, 4);
          break;
        }
        case RESTRICTED_MANOEUVRABILITY: {
          wxPoint diamond[4];
          diamond[0] = wxPoint(4, 0);
          diamond[1] = wxPoint(0, -6);
          diamond[2] = wxPoint(-4, 0);
          diamond[3] = wxPoint(0, 6);
          dc.StrokePolygon(4, diamond, TargetPoint.x, TargetPoint.y - 11);
          dc.StrokeCircle(TargetPoint.x, TargetPoint.y, 4);
          dc.StrokeCircle(TargetPoint.x, TargetPoint.y - 22, 4);
          break;
          break;
        }
        case CONSTRAINED_BY_DRAFT: {
          wxPoint can[4] = {wxPoint(-3, 0), wxPoint(3, 0), wxPoint(3, -16),
                            wxPoint(-3, -16)};
          dc.StrokePolygon(4, can, TargetPoint.x, TargetPoint.y);
          break;
        }
        case NOT_UNDER_COMMAND: {
          dc.StrokeCircle(TargetPoint.x, TargetPoint.y, 4);
          dc.StrokeCircle(TargetPoint.x, TargetPoint.y - 9, 4);
          break;
        }
        case FISHING: {
          wxPoint tri[3];
          tri[0] = wxPoint(-4, 0);
          tri[1] = wxPoint(4, 0);
          tri[2] = wxPoint(0, -9);
          dc.StrokePolygon(3, tri, TargetPoint.x, TargetPoint.y);
          tri[0] = wxPoint(0, -9);
          tri[1] = wxPoint(4, -18);
          tri[2] = wxPoint(-4, -18);
          dc.StrokePolygon(3, tri, TargetPoint.x, TargetPoint.y);
          break;
        }
        case AGROUND: {
          dc.StrokeCircle(TargetPoint.x, TargetPoint.y, 4);
          dc.StrokeCircle(TargetPoint.x, TargetPoint.y - 9, 4);
          dc.StrokeCircle(TargetPoint.x, TargetPoint.y - 18, 4);
          break;
        }
        case HSC:
        case WIG: {
          dc.SetBrush(target_brush);

          wxPoint arrow1[3] = {wxPoint(-4, 20), wxPoint(0, 27), wxPoint(4, 20)};
          transrot_pts(3, arrow1, sin_theta, cos_theta, TargetPoint);
          dc.StrokePolygon(3, arrow1);

          wxPoint arrow2[3] = {wxPoint(-4, 27), wxPoint(0, 34), wxPoint(4, 27)};
          transrot_pts(3, arrow2, sin_theta, cos_theta, TargetPoint);
          dc.StrokePolygon(3, arrow2);
          break;
        }
      }
    }  // end if (targetscale > 75)

    //        Draw the inactive cross-out line
    if (!td->b_active) {
      wxPoint p1 = transrot(wxPoint((int)-14 * targetscale / 100, 0), sin_theta,
                            cos_theta, TargetPoint);
      wxPoint p2 = transrot(wxPoint((int)14 * targetscale / 100, 0), sin_theta,
                            cos_theta, TargetPoint);

      dc.SetPen(wxPen(UBLCK, 2));
      dc.StrokeLine(p1.x, p1.y, p2.x, p2.y);
    }

    //    European Inland AIS define a "stbd-stbd" meeting sign, a blue paddle.
    //    Symbolize it if set by most recent message
    //    Blue paddel is used while "not engaged"(1) or "engaged"(2)  (3 ==
    //    "reserved")
    if (td->blue_paddle && td->blue_paddle < 3) {
      wxPoint ais_flag_icon[4];
      int penWidth = 2;

      if (g_bInlandEcdis) {
        if (b_hdgValid) {
          ais_flag_icon[0] = wxPoint(-4, 4);
          ais_flag_icon[1] = wxPoint(-4, 11);
          ais_flag_icon[2] = wxPoint(-11, 11);
          ais_flag_icon[3] = wxPoint(-11, 4);
          transrot_pts(4, ais_flag_icon, sin_theta, cos_theta, TargetPoint);
        } else {
          ais_flag_icon[0] = wxPoint(TargetPoint.x - 4, TargetPoint.y + 4);
          ais_flag_icon[1] = wxPoint(TargetPoint.x - 4, TargetPoint.y - 3);
          ais_flag_icon[2] = wxPoint(TargetPoint.x + 3, TargetPoint.y - 3);
          ais_flag_icon[3] = wxPoint(TargetPoint.x + 3, TargetPoint.y + 4);
        }

        dc.SetPen(wxPen(GetGlobalColor(_T ( "CHWHT" )), penWidth));

      } else {
        ais_flag_icon[0] =
            wxPoint((int)-8 * targetscale / 100, (int)-6 * targetscale / 100);
        ais_flag_icon[1] =
            wxPoint((int)-2 * targetscale / 100, (int)18 * targetscale / 100);
        ais_flag_icon[2] = wxPoint((int)-2 * targetscale / 100, 0);
        ais_flag_icon[3] =
            wxPoint((int)-2 * targetscale / 100, (int)-6 * targetscale / 100);
        transrot_pts(4, ais_flag_icon, sin_theta, cos_theta, TargetPoint);

        if (targetscale < 100) penWidth = 1;
        dc.SetPen(wxPen(GetGlobalColor(_T ( "CHWHT" )), penWidth));
      }
      if (td->blue_paddle == 1) {
        ais_flag_icon[1] = ais_flag_icon[0];
        ais_flag_icon[2] = ais_flag_icon[3];
      }

      dc.SetBrush(wxBrush(GetGlobalColor(_T ( "UINFB" ))));
      dc.StrokePolygon(4, ais_flag_icon);
    }
  }

  if ((g_bShowAISName) && (targetscale > 75)) {
    int true_scale_display = (int)(floor(vp.chart_scale / 100.) * 100);
    if (true_scale_display <
        g_Show_Target_Name_Scale) {  // from which scale to display name

      wxString tgt_name = td->GetFullName();
      tgt_name = tgt_name.substr(0, tgt_name.find(_T ( "Unknown" ), 0));

      if (tgt_name != wxEmptyString) {
        dc.SetFont(*FontMgr::Get().GetFont(_("AIS Target Name"), 12));
        dc.SetTextForeground(FontMgr::Get().GetFontColor(_("AIS Target Name")));

        int w, h;
        dc.GetTextExtent(tgt_name, &w, &h);

        if ((td->COG > 90) && (td->COG < 180))
          dc.DrawText(tgt_name, TargetPoint.x + 10, TargetPoint.y - h);
        else
          dc.DrawText(tgt_name, TargetPoint.x + 10, TargetPoint.y + 0.5 * h);

      }  // If name do not empty
    }    // if scale
  }

  //  Draw tracks if enabled
  //  Check the Special MMSI Properties array
  bool b_noshow = false;
  bool b_forceshow = false;
  for (unsigned int i = 0; i < g_MMSI_Props_Array.GetCount(); i++) {
    if (td->MMSI == g_MMSI_Props_Array[i]->MMSI) {
      MMSIProperties *props = g_MMSI_Props_Array[i];
      if (TRACKTYPE_NEVER == props->TrackType) {
        b_noshow = true;
        break;
      } else if (TRACKTYPE_ALWAYS == props->TrackType) {
        b_forceshow = true;
        break;
      } else
        break;
    }
  }

  if ((!b_noshow && td->b_show_track) || b_forceshow) {
    //  create vector of x-y points
    int TrackLength = td->m_ptrack->GetCount();
    int TrackPointCount;
    wxPoint *TrackPoints = 0;
    if (TrackLength > 1) {
      TrackPoints = new wxPoint[TrackLength];
      wxAISTargetTrackListNode *node = td->m_ptrack->GetFirst();
      for (TrackPointCount = 0; node && (TrackPointCount < TrackLength);
           TrackPointCount++) {
        AISTargetTrackPoint *ptrack_point = node->GetData();
        GetCanvasPointPix(vp, cp, ptrack_point->m_lat, ptrack_point->m_lon,
                          &TrackPoints[TrackPointCount]);
        node = node->GetNext();
      }
    }

    wxColour c = GetGlobalColor(_T ( "CHMGD" ));
    dc.SetPen(wxPen(c, 2 * AIS_nominal_line_width_pix));

#ifdef ocpnUSE_GL
#ifndef USE_ANDROID_GLES2
    if (TrackLength > 1) {
      glLineWidth(2);
      glColor3ub(c.Red(), c.Green(), c.Blue());
      glBegin(GL_LINE_STRIP);

      for (TrackPointCount = 0; TrackPointCount < TrackLength;
           TrackPointCount++)
        glVertex2i(TrackPoints[TrackPointCount].x,
                   TrackPoints[TrackPointCount].y);

      glEnd();
    }
#else
    if (TrackLength > 1) dc.DrawLines(TrackPointCount, TrackPoints);
#endif

#else
    if (dc.GetDC() && (TrackLength > 1))
      dc.StrokeLines(TrackPointCount, TrackPoints);

#endif

    if (TrackLength > 1) delete[] TrackPoints;

  }  // Draw tracks
}

void AISDraw(ocpnDC &dc, ViewPort &vp, ChartCanvas *cp) {
  if (!g_pAIS) return;

  // Toggling AIS display on and off
  if (cp != NULL) {
    if (!cp->GetShowAIS()) return;
  }

  AISSetMetrics();

  AIS_Target_Hash::iterator it;
  AIS_Target_Hash *current_targets = g_pAIS->GetTargetList();
  //      Iterate over the AIS Target Hashmap but only for the main chartcanvas.
  //      For secundairy canvasses we use the same value for the AIS importance
  bool go = false;

  if (cp == NULL) {
    go = true;
  } else if (cp->m_canvasIndex == 0) {
    go = true;
  }

  if (go) {
    for (it = (*current_targets).begin(); it != (*current_targets).end();
         ++it) {
      // calculate the importancefactor for each target
      AIS_Target_Data *td = it->second;
      double So, Cpa, Rang, Siz = 0.0;
      So = g_ScaledNumWeightSOG / 12 *
           td->SOG;  // 0 - 12 knts gives 0 - g_ScaledNumWeightSOG weight
      if (So > g_ScaledNumWeightSOG) So = g_ScaledNumWeightSOG;

      if (td->bCPA_Valid) {
        Cpa = g_ScaledNumWeightCPA - g_ScaledNumWeightCPA / 4 * td->CPA;
        // if TCPA is positief (target is coming closer), make weight of CPA
        // bigger
        if (td->TCPA > .0) Cpa = Cpa + Cpa * g_ScaledNumWeightTCPA / 100;
        if (Cpa < .0) Cpa = .0;  // if CPA is > 4
      } else
        Cpa = .0;

      Rang = g_ScaledNumWeightRange / 10 * td->Range_NM;
      if (Rang > g_ScaledNumWeightRange) Rang = g_ScaledNumWeightRange;
      Rang = g_ScaledNumWeightRange - Rang;

      Siz = g_ScaledNumWeightSizeOfT / 30 * (td->DimA + td->DimB);
      if (Siz > g_ScaledNumWeightSizeOfT) Siz = g_ScaledNumWeightSizeOfT;
      td->importance = (float)So + Cpa + Rang + Siz;
    }
  }

  // If needed iterate over all targets, check if they fit in the viewport and
  // if yes add the importancefactor to a sorted list
  AISImportanceSwitchPoint = 0.0;

  float *Array = new float[g_ShowScaled_Num];
  for (int i = 0; i < g_ShowScaled_Num; i++) Array[i] = 0.0;

  int LowestInd = 0;
  if (cp != NULL) {
    if (cp->GetAttenAIS()) {
      for (it = (*current_targets).begin(); it != (*current_targets).end();
           ++it) {
        AIS_Target_Data *td = it->second;
        if (vp.GetBBox().Contains(td->Lat, td->Lon)) {
          if (td->importance > AISImportanceSwitchPoint) {
            Array[LowestInd] = td->importance;

            AISImportanceSwitchPoint = Array[0];
            LowestInd = 0;
            for (int i = 1; i < g_ShowScaled_Num; i++) {
              if (Array[i] < AISImportanceSwitchPoint) {
                AISImportanceSwitchPoint = Array[i];
                LowestInd = i;
              }
            }
          }
        }
      }
    }
  }
  delete[] Array;

  //    Draw all targets in three pass loop, sorted on SOG, GPSGate & DSC on top
  //    This way, fast targets are not obscured by slow/stationary targets
  for (it = (*current_targets).begin(); it != (*current_targets).end(); ++it) {
    AIS_Target_Data *td = it->second;
    if ((td->SOG < g_ShowMoored_Kts) &&
        !((td->Class == AIS_GPSG_BUDDY) || (td->Class == AIS_DSC))) {
      AISDrawTarget(td, dc, vp, cp);
    }
  }

  for (it = (*current_targets).begin(); it != (*current_targets).end(); ++it) {
    AIS_Target_Data *td = it->second;
    if ((td->SOG >= g_ShowMoored_Kts) &&
        !((td->Class == AIS_GPSG_BUDDY) || (td->Class == AIS_DSC))) {
      AISDrawTarget(td, dc, vp, cp);  // yes this is a doubling of code;(
      if (td->importance > 0) AISDrawTarget(td, dc, vp, cp);
    }
  }

  for (it = (*current_targets).begin(); it != (*current_targets).end(); ++it) {
    AIS_Target_Data *td = it->second;
    if ((td->Class == AIS_GPSG_BUDDY) || (td->Class == AIS_DSC))
      AISDrawTarget(td, dc, vp, cp);
  }
}

bool AnyAISTargetsOnscreen(ChartCanvas *cc, ViewPort &vp) {
  if (!g_pAIS) return false;

  if (!cc->GetShowAIS()) return false;  //

  //      Iterate over the AIS Target Hashmap
  AIS_Target_Hash::iterator it;
  AIS_Target_Hash *current_targets = g_pAIS->GetTargetList();

  for (it = (*current_targets).begin(); it != (*current_targets).end(); ++it) {
    AIS_Target_Data *td = it->second;
    if (vp.GetBBox().Contains(td->Lat, td->Lon)) return true;  // yep
  }

  return false;
}
