{"version":3,"file":"TerminatorSymbol.mjs","sources":["../../../../../../../packages/sdk/plugins/AreaView/layer/TerminatorSymbol.ts"],"sourcesContent":["import L from 'leaflet'\nimport dayjs from 'dayjs'\nimport type { MyMap } from '@map-sdk/sdk/ShipxyAPISDK'\n\n// 常量定义\nconst TERMINATOR_UPDATE_INTERVAL = 300000 // 5分钟 (毫秒)\nconst LONGITUDE_STEP = 1 // 经度步长\nconst DAYS_IN_YEAR = 365\nconst EARTH_TILT = 23.44\nconst HOURS_IN_DAY = 24\nconst DEG_TO_RAD = Math.PI / 180\nconst RAD_TO_DEG = 180 / Math.PI\nconst TIME_BUTTONS = [-18, -12, -6, -1, 0, +1, +6, +12, +18]\nconst DAYS_OF_WEEK = ['日', '一', '二', '三', '四', '五', '六']\n\n// 太阳位置接口\ninterface SunPosition {\n  altitude: number\n  azimuth: number\n  latitude: number\n  longitude: number\n}\n\nclass Terminator {\n  private hourOffset = 0\n  private updateIntervalId: number | null = null\n  private clockTimerId: number | null = null\n  private isInitialized = false\n  private layerGroup: L.LayerGroup | null = null\n  private readonly map: MyMap\n\n  constructor(map: MyMap) {\n    this.map = map\n  }\n\n  /**\n   * 计算太阳赤纬（太阳直射点的纬度）\n   * @param dayOfYear 一年中的第几天 (0-364)\n   * @returns 太阳赤纬（角度）\n   */\n  private calculateSolarDeclination(dayOfYear: number): number {\n    return (\n      EARTH_TILT *\n      Math.sin((360 / DAYS_IN_YEAR) * (dayOfYear - 81) * DEG_TO_RAD)\n    )\n  }\n\n  /**\n   * 计算太阳时角\n   * @param date UTC日期对象\n   * @returns 太阳时角（角度）\n   */\n  private calculateSolarHourAngle(date: Date): number {\n    const utcHours =\n      date.getUTCHours() +\n      date.getUTCMinutes() / 60 +\n      date.getUTCSeconds() / 3600\n    return (utcHours - 12) * 15\n  }\n\n  /**\n   * 计算晨昏线纬度\n   * @param longitude 经度\n   * @param solarDeclination 太阳赤纬\n   * @param solarHourAngle 太阳时角\n   * @returns 晨昏线纬度\n   */\n  private calculateTerminatorLatitude(\n    longitude: number,\n    solarDeclination: number,\n    solarHourAngle: number\n  ): number {\n    const terminatorLat =\n      Math.atan(\n        -Math.cos((longitude + solarHourAngle) * DEG_TO_RAD) /\n          Math.tan(solarDeclination * DEG_TO_RAD)\n      ) * RAD_TO_DEG\n\n    return terminatorLat\n  }\n\n  /**\n   * 清除所有图层和定时器\n   */\n  private clearResources(): void {\n    try {\n      this.isInitialized = false\n\n      // 清除图层\n      if (this.layerGroup) {\n        this.layerGroup.clearLayers()\n        this.map.removeLayer(this.layerGroup)\n        this.layerGroup = null\n      }\n\n      // 清除定时器\n      if (this.updateIntervalId) {\n        clearInterval(this.updateIntervalId)\n        this.updateIntervalId = null\n      }\n\n      if (this.clockTimerId) {\n        clearInterval(this.clockTimerId)\n        this.clockTimerId = null\n      }\n\n      this.hourOffset = 0\n    } catch (error) {\n      console.error('清除资源时出错:', error)\n    }\n  }\n\n  /**\n   * 初始化UI组件\n   */\n  private initializeUI(): void {\n    if (this.isInitialized) return\n\n    try {\n      // 创建图层组\n      this.layerGroup = L.layerGroup().addTo(this.map)\n\n      // 生成时间按钮HTML\n      const hourButtons = TIME_BUTTONS.map((hour) => {\n        const formattedHour = hour > 0 ? `+${hour}` : hour.toString()\n        return hour === 0\n          ? `<span data-hour=\"${hour}\" class=\"on\">现在（北京时间）</span>`\n          : `<span data-hour=\"${hour}\">${formattedHour}小时</span>`\n      }).join('')\n\n      const html = `\n        <div id='terminatorcontainer' class='terminatorcontainer'>\n          <div class='terminatorbox'>\n            <div><label>UTC+8 时间: </label><label id='utc-time'></label></div>\n            <div class='time-buttons'>${hourButtons}</div>\n            <div class='terminator-close'></div>\n          </div>\n        </div>\n      `\n\n      // 避免重复插入HTML\n      if (!document.querySelector('#terminatorcontainer')) {\n        document.body.insertAdjacentHTML('beforeend', html)\n      }\n\n      // 绑定按钮事件\n      document.querySelectorAll('.time-buttons span').forEach((span) => {\n        span.addEventListener('click', () => {\n          document.querySelectorAll('.time-buttons span').forEach((sibling) => {\n            sibling.classList.remove('on')\n          })\n\n          span.classList.add('on')\n          const hour = Number.parseInt(\n            (span as HTMLElement).dataset.hour || '0'\n          )\n          this.showTerminator(hour)\n        })\n      })\n\n      // 绑定关闭按钮事件\n      document\n        .querySelector('.terminator-close')\n        ?.addEventListener('click', () => {\n          this.hideTerminatorUI()\n        })\n\n      // 更新时间显示\n      this.updateClockDisplay()\n      this.clockTimerId = window.setInterval(\n        () => this.updateClockDisplay(),\n        1000\n      )\n\n      this.isInitialized = true\n    } catch (error) {\n      console.error('初始化UI失败:', error)\n    }\n  }\n\n  /**\n   * 显示晨昏线\n   * @param hourOffset 小时偏移量\n   */\n  private showTerminator(hourOffset = 0): void {\n    console.info(this.layerGroup)\n    if (!this.layerGroup) return\n    this.layerGroup.clearLayers()\n    this.hourOffset = hourOffset\n    const now = new Date(Date.now() + (hourOffset || 0) * 3600 * 1e3)\n    const coords = this.generateTerminator(now)\n    const maxCoords = this.getMaxFormTerminatorCoords(coords)\n\n    const cycles = [-360, 0, 360]\n    const sunLat = this.getSolarDeclination(new Date(now))\n    const sunLng = this.getSolarLongitude(new Date(now))\n\n    cycles.forEach((offset) => {\n      const offsetCoords = coords.map((coord: any) => [\n        coord[0],\n        coord[1] + offset,\n      ])\n      const outer = maxCoords.map((coord: any) => [coord[0], coord[1] + offset])\n\n      // 创建晨昏线多边形\n      this.layerGroup?.addLayer(\n        L.polygon([outer, offsetCoords], {\n          color: '#ff9900',\n          weight: 2,\n          fillColor: '#333',\n          fillOpacity: 0.3,\n          interactive: false,\n        })\n      )\n\n      // 添加太阳位置标记\n      if (this.layerGroup) {\n        this.layerGroup.addLayer(\n          L.marker([sunLat, sunLng + offset], {\n            icon: L.icon({\n              iconUrl: 'https://www.shipxy.com/Content/images/sun-pos.png',\n              iconSize: [45, 45],\n              iconAnchor: [22, 22],\n            }),\n            interactive: false,\n          })\n        )\n      }\n    })\n  }\n  private getSolarDeclination(date: Date): number {\n    const dayOfYear = Math.floor(\n      (date.getTime() - new Date(date.getFullYear(), 0, 0).getTime()) /\n        (1000 * 60 * 60 * HOURS_IN_DAY)\n    )\n    return this.calculateSolarDeclination(dayOfYear)\n  }\n\n  private getSolarLongitude(date: Date): number {\n    const dayOfYear = Math.floor(\n      (date.getTime() - new Date(date.getFullYear(), 0, 0).getTime()) /\n        (1000 * 60 * 60 * HOURS_IN_DAY)\n    )\n    const solarDeclination = this.calculateSolarDeclination(dayOfYear)\n    const solarHourAngle = this.calculateSolarHourAngle(date)\n\n    let minLat = 90\n    let minLng = -180\n    for (let lon = -180; lon <= 180; lon += LONGITUDE_STEP) {\n      const lat = this.calculateTerminatorLatitude(\n        lon,\n        solarDeclination,\n        solarHourAngle\n      )\n      if (lat < minLat) {\n        minLat = lat\n        minLng = lon\n      }\n    }\n    return minLng\n  }\n  private generateTerminator(date: Date): any[] {\n    const dayOfYear = Math.floor(\n      (date.getTime() - new Date(date.getFullYear(), 0, 0).getTime()) /\n        (1000 * 60 * 60 * HOURS_IN_DAY)\n    )\n    const solarDeclination = this.calculateSolarDeclination(dayOfYear)\n    const solarHourAngle = this.calculateSolarHourAngle(date)\n\n    const points: any[] = []\n    for (let lon = -180; lon <= 180; lon += 1) {\n      const lat = this.calculateTerminatorLatitude(\n        lon,\n        solarDeclination,\n        solarHourAngle\n      )\n      points.push([lat, lon])\n    }\n\n    if (this.isSunAtTropic(date) >= 0) points.push([-90, 180], [-90, -180])\n    else points.push([90, 180], [90, -180])\n\n    return points\n  }\n\n  private isSunAtTropic(date: Date): number {\n    const dayOfYear = Math.floor(\n      (date.getTime() - new Date(date.getFullYear(), 0, 0).getTime()) /\n        (1000 * 60 * 60 * HOURS_IN_DAY)\n    )\n    const solarDeclination = this.calculateSolarDeclination(dayOfYear)\n    if (solarDeclination > 0) {\n      return -1\n    } else if (solarDeclination < 0) {\n      return 1\n    } else {\n      return 0\n    }\n  }\n\n  private getMaxFormTerminatorCoords(terminatorCoords: any[]): any[] {\n    let latMax = -90\n    let latMin = 90\n    let lngMax = -180\n    let lngMin = 180\n    terminatorCoords.forEach((c: any) => {\n      if (c[0] > latMax) latMax = c[0]\n      if (c[0] < latMin) latMin = c[0]\n      if (c[1] > lngMax) lngMax = c[1]\n      if (c[1] < lngMin) lngMin = c[1]\n    })\n\n    return latMin === -90\n      ? [\n          [latMin, lngMin],\n          [latMin, lngMax],\n          [90, 180],\n          [90, -180],\n        ]\n      : [\n          [-90, -180],\n          [-90, 180],\n          [latMax, lngMax],\n          [latMax, lngMin],\n        ]\n  }\n\n  /**\n   * 隐藏晨昏线UI\n   */\n  private hideTerminatorUI(): void {\n    const container = document.querySelector('#terminatorcontainer')\n    if (container) {\n      ;(container as HTMLElement).style.display = 'none'\n    }\n  }\n\n  /**\n   * 更新时钟显示\n   */\n  private updateClockDisplay(): void {\n    try {\n      const timeElement = document.querySelector('#utc-time')\n      if (timeElement) {\n        const now = dayjs()\n        const dayOfWeek = DAYS_OF_WEEK[now.day()]\n        timeElement.textContent = `${now.format(\n          'YYYY-MM-DD HH:mm:ss'\n        )}，星期${dayOfWeek}`\n      }\n    } catch (error) {\n      console.error('更新时间显示失败:', error)\n    }\n  }\n\n  /**\n   * 切换晨昏线显示状态\n   * @param event 触发事件的元素\n   */\n  toggleVisibility(isShow = false): void {\n    try {\n      const container = document.querySelector('#terminatorcontainer')\n      if (!isShow) {\n        this.clearResources()\n        if (container) (container as HTMLElement).style.display = 'none'\n      } else {\n        this.initializeUI()\n        this.showTerminator()\n        if (container) (container as HTMLElement).style.display = 'block'\n\n        // 设置定期更新\n        this.updateIntervalId = window.setInterval(() => {\n          this.showTerminator(this.hourOffset)\n        }, TERMINATOR_UPDATE_INTERVAL)\n        this.map.setZoom(2)\n      }\n    } catch (error) {\n      console.error('切换显示状态失败:', error)\n    }\n  }\n}\n\n/**\n * 创建晨昏线控制器\n * @param map 地图实例\n * @returns Terminator 实例\n */\nconst terminatorSymbol = (map: MyMap): Terminator => new Terminator(map)\n\nexport { terminatorSymbol, Terminator }\n"],"names":["m","Math","PI","M","S","L","p","constructor","t","this","hourOffset","updateIntervalId","clockTimerId","isInitialized","layerGroup","map","calculateSolarDeclination","sin","calculateSolarHourAngle","getUTCHours","getUTCMinutes","getUTCSeconds","calculateTerminatorLatitude","e","a","atan","cos","tan","clearResources","clearLayers","removeLayer","clearInterval","initializeUI","c","addTo","r","i","toString","join","document","querySelector","body","insertAdjacentHTML","querySelectorAll","forEach","addEventListener","o","classList","remove","add","Number","parseInt","dataset","hour","showTerminator","hideTerminatorUI","updateClockDisplay","window","setInterval","Date","now","generateTerminator","getMaxFormTerminatorCoords","getSolarDeclination","n","getSolarLongitude","l","d","y","s","f","addLayer","polygon","color","weight","fillColor","fillOpacity","interactive","marker","icon","iconUrl","iconSize","iconAnchor","floor","getTime","getFullYear","push","isSunAtTropic","style","display","T","day","textContent","format","toggleVisibility","setZoom","b","h"],"mappings":"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"}