Exemplo n.º 1
0
        protected override Message OnWork(Action <Message> sender)
        {
            try
            {
                var bytes = m_client.GetByteArrayAsync(@"https://www.weatheri.co.kr/special/special03.php");

                bytes.Wait();


                var byteArray = bytes.Result.ToArray();
                var encoding  = Encoding.UTF8;
                var html      = encoding.GetString(byteArray, 0, byteArray.Length);

                if (string.IsNullOrWhiteSpace(html) == false)
                {
                    int offset = 0;

                    string date        = GetTableContent(html, "진원시", offset, out offset);
                    string location    = GetTableContent(html, "<td", offset, out offset);
                    string scaleText   = GetTableContent(html, "<td", offset, out offset);
                    string description = GetTableContent(html, "<td", offset, out offset);

                    if (string.IsNullOrEmpty(date) || string.IsNullOrEmpty(location) ||
                        string.IsNullOrEmpty(scaleText) || string.IsNullOrEmpty(description))
                    {
                        return(null);
                    }

                    var buffer = new StringBuilder();
                    buffer.Append("진원시 : ");
                    buffer.AppendLine(date);
                    buffer.Append("진앙 : ");
                    buffer.AppendLine(location);
                    buffer.Append("규모 : ");
                    buffer.AppendLine(scaleText);
                    buffer.Append("설명 : ");
                    buffer.AppendLine(description);

                    string message = buffer.ToString();

                    double scale = 0.0;

                    if (double.TryParse(scaleText, out scale) == false)
                    {
                        return(null);
                    }
                    else if (string.IsNullOrEmpty(m_latestNoti))
                    {
                        m_latestNoti = message;

                        m_logger.PushLog(message);
                    }
                    else if (m_latestNoti != message)
                    {
                        m_latestNoti = message;

                        var msgLevel = Message.Priority.Normal;

                        var koreaKeywords = new string[]
                        {
                            "경북", "경남", "경기", "전남", "전북", "제주", "서울", "충남", "충북",
                            "북도", "남도", "광역시", "특별",
                            "부산", "대구", "인천", "광주", "대전", "울산", "세종", "강원",
                        };

                        if (koreaKeywords.Any((text) => location.Contains(text)))
                        {
                            msgLevel = Message.Priority.High;


                            if (scale > 0)
                            {
                                buffer.AppendLine();

                                buffer.Append(Earthquake.GetKnowHowFromMScale(scale));
                            }
                        }

                        return(new Message()
                        {
                            Level = msgLevel,
                            Sender = "웨더아이",
                            Text = buffer.ToString(),
                        });
                    }
                    else
                    {
                        Console.Write('i');
                    }
                }
            }
            catch (Exception exp)
            {
                Console.WriteLine(exp.Message);
                Console.WriteLine(exp.StackTrace);


                Thread.Sleep(8000);

                m_client = new HttpClient();
            }


            return(null);
        }
Exemplo n.º 2
0
        private Message HandleMmi(string body)
        {
            const double ClusterDistance = 60.0;
            const int    MinClusterSize  = 4;

            if (m_stations.Count <= 0)
            {
                return(null);
            }

            var mmiData = new List <int>();

            for (int i = 0; i < body.Length; i += 4)
            {
                if (mmiData.Count >= m_stations.Count)
                {
                    break;
                }

                int rawMmi = Convert.ToInt32(body.Substring(i, 4), 2);
                mmiData.Add(rawMmi);
            }

            if (mmiData.Count < m_stations.Count)
            {
                return(null);
            }

            // 관측소 진도 갱신.
            for (int i = 0; i < m_stations.Count; ++i)
            {
                var stn    = m_stations[i];
                int rawMmi = mmiData[i];

                stn.UpdateMmi(rawMmi, StnMmiLife);
            }

            int maxClusterMmi   = 0;
            var largeClusterMmi = new List <int> {
                0
            };
            string maxClusterLoc = string.Empty;

            bool[] visited = new bool[m_stations.Count];
            for (int i = 0; i < m_stations.Count; ++i)
            {
                if (visited[i])
                {
                    continue;
                }

                var clusterMmi = new List <int> {
                    0
                };
                int centerStn = -1;

                var leftStns = new Queue <int>();
                leftStns.Enqueue(i);

                while (leftStns.Count > 0)
                {
                    int current = leftStns.Dequeue();

                    if (visited[current])
                    {
                        continue;
                    }
                    visited[current] = true;

                    var stn = m_stations[current];
                    int mmi = stn.Mmi;

                    // 진도 2 이상이거나 진도 1 안에서 큰 축에 속하는 경우에만 노드로 취급.
                    if (mmi < 2 && stn.RawMmi < 14)
                    {
                        continue;
                    }

                    clusterMmi.Add(mmi);

                    if (centerStn < 0 || stn.Mmi > m_stations[centerStn].Mmi)
                    {
                        centerStn = current;
                    }

                    double centerX = (stn.Longitude - 124.5) * 113;
                    double centerY = (38.9 - stn.Latitude) * 138.4;

                    for (int next = 0; next < m_stations.Count; ++next)
                    {
                        if (visited[next])
                        {
                            continue;
                        }

                        var nextStn = m_stations[next];

                        double subX = (nextStn.Longitude - 124.5) * 113 - centerX;
                        double subY = (38.9 - nextStn.Latitude) * 138.4 - centerY;

                        double distanceSqr = subX * subX + subY * subY;
                        if (distanceSqr < ClusterDistance * ClusterDistance)
                        {
                            leftStns.Enqueue(next);
                        }
                    }
                }

                if (centerStn >= 0 && m_stations[centerStn].Mmi > maxClusterMmi)
                {
                    maxClusterMmi   = m_stations[centerStn].Mmi;
                    largeClusterMmi = clusterMmi;
                    maxClusterLoc   = m_stations[centerStn].Location;
                }
            }

            if (maxClusterMmi <= m_maxStnMmi)
            {
                Message msg = null;

                // 안정화 되었으면.
                if (maxClusterMmi < StnMmiTrigger)
                {
                    // NOTE: 큰 지진의 경우 신속히 지진 속보가 발표되므로 이 부분은 실행되지 않을 수도 있음.

                    // 트리거를 초과했었으면 진도 지도 송출.
                    if (m_maxStnMmi >= StnMmiTrigger)
                    {
                        string filePath = SaveStnMaxMmiToFile();

                        // NOTE: 법적 문제로 제공 중단.

                        /*msg = new Message()
                         * {
                         *  Level = Message.Priority.Normal,
                         *  Sender = "기상청 실시간 지진감시",
                         *  Text = filePath,
                         * };*/
                    }

                    // 관측소 진도 초기화.
                    foreach (var stn in m_stations)
                    {
                        stn.ResetMmi();
                    }

                    // 트리거 레벨 초기화.
                    m_maxStnMmi = StnMmiTrigger - 1;
                }

                return(msg);
            }

            // 진도 I 개수는 가중치를 낮게 두고 클러스터 크기 확인.
            int mmi1Cnt = largeClusterMmi.Count((m) => m == 1);

            if ((largeClusterMmi.Count - mmi1Cnt + mmi1Cnt * 0.5) >= MinClusterSize + 1)
            {
                int[] mmiCnt = new int[14];
                for (int mmi = 0; mmi < mmiCnt.Length; ++mmi)
                {
                    mmiCnt[mmi] = largeClusterMmi.Count((m) => m == mmi);
                }

                var buffer = new StringBuilder();

                if (m_maxStnMmi >= StnMmiTrigger)
                {
                    buffer.AppendLine("⚠️ 감시 화면에 대한 요약정보가 갱신되었습니다.");
                }
                else
                {
                    buffer.AppendLine("⚠️ 실시간 감시 화면에 대한 요약정보입니다.");
                }

                if (!string.IsNullOrEmpty(maxClusterLoc))
                {
                    buffer.AppendLine($"관측소 위치 : {maxClusterLoc} 인근");
                }

                buffer.AppendLine($"최대진도 : {Earthquake.MMIToString(maxClusterMmi)}({maxClusterMmi})");

                for (int mmi = mmiCnt.Length - 1; mmi >= 1; --mmi)
                {
                    if (mmiCnt[mmi] > 0)
                    {
                        buffer.Append($"진도 {Earthquake.MMIToString(mmi)}({mmi}) : ");
                        buffer.AppendLine($"{mmiCnt[mmi]}건");
                    }
                }

                if (maxClusterMmi >= 5)
                {
                    buffer.AppendLine("흔들림이 느껴질 때 : https://www.weather.go.kr/pews/man/m.html");
                }

                buffer.AppendLine("실제 지진은 신속/상세정보로 발표됩니다.");
                buffer.AppendLine();
                buffer.Append(Earthquake.GetKnowHowFromMMI(maxClusterMmi));

                // 임시 트리거 레벨을 높힘.
                m_maxStnMmi = maxClusterMmi;

                // NOTE: 법적 문제로 제공 중단.

                /*return new Message()
                 * {
                 *  Level = ((maxClusterMmi >= 5) ? Message.Priority.Critical : Message.Priority.High),
                 *  Sender = "기상청 실시간 지진감시",
                 *  Text = buffer.ToString().TrimEnd(),
                 * };*/
            }

            return(null);
        }
Exemplo n.º 3
0
        private Message HandleEqk(int phase, string body, byte[] infoBytes, out PointF epicenter)
        {
            string data   = body.Substring(body.Length - (MaxEqkStrLen * 8 + MaxEqkInfoLen));
            string eqkStr = WebUtility.UrlDecode(Encoding.UTF8.GetString(infoBytes)).Trim();

            double origLat       = 30 + (double)Convert.ToInt32(data.Substring(0, 10), 2) / 100;
            double origLon       = 124 + (double)Convert.ToInt32(data.Substring(10, 10), 2) / 100;
            double eqkMag        = (double)Convert.ToInt32(data.Substring(20, 7), 2) / 10;
            double eqkDep        = (double)Convert.ToInt32(data.Substring(27, 10), 2) / 10;
            int    eqkUnixTime   = Convert.ToInt32(data.Substring(37, 32), 2);
            var    eqkTime       = DateTimeOffset.FromUnixTimeSeconds(eqkUnixTime + 9 * 3600).AddHours(9.0);
            string eqkId         = "20" + Convert.ToInt32(data.Substring(69, 26), 2); // TODO: 22세기가 되면 "20"이 아니게 되는건가?
            int    eqkIntens     = Convert.ToInt32(data.Substring(95, 4), 2);
            string eqkMaxAreaStr = data.Substring(99, 17);
            var    eqkMaxArea    = new List <string>();

            if (eqkMaxAreaStr != new string('1', eqkMaxAreaStr.Length))
            {
                for (int i = 0; i < eqkMaxAreaStr.Length; ++i)
                {
                    if (eqkMaxAreaStr[i] == '1')
                    {
                        eqkMaxArea.Add(AreaNames[i]);
                    }
                }
            }

            epicenter = new PointF(
                (float)((origLon - 124.5) * 113 - 4),
                (float)((38.9 - origLat) * 138.4 - 7));

            string  alarmId = eqkId + phase;
            Message msg     = null;

            // 페이즈가 넘어갔으며 이전에 전송한 것과 동일한 알람이 아니라면.
            if (phase > m_prevPhase && alarmId != m_prevAlarmId)
            {
                m_gridEqkId = eqkId;

                if (phase == 2)
                {
                    // 발생 시각, 규모, 최대 진도, 문구 정도는 부정확할 수 있어도 첫 정보에 포함되는 듯.

                    var buffer = new StringBuilder();
                    buffer.AppendLine("⚠️ 지진 신속정보가 발표되었습니다.");
                    buffer.AppendLine($"정보 : {eqkStr}");
                    buffer.AppendLine($"발생 시각 : {eqkTime:yyyy-MM-dd HH:mm:ss}");
                    buffer.AppendLine($"추정 규모 : {eqkMag:N1}");
                    buffer.AppendLine($"최대 진도 : {Earthquake.MMIToString(eqkIntens)}({eqkIntens})");
                    buffer.AppendLine("대피 요령 : https://www.weather.go.kr/pews/man/m.html");
                    buffer.AppendLine("수동으로 분석한 정보는 추후 발표될 예정입니다.");
                    buffer.AppendLine();
                    buffer.Append(Earthquake.GetKnowHowFromMMI(eqkIntens));

                    m_prevAlarmId = alarmId;

                    m_fcmMessage = new PewsJson
                    {
                        time  = (eqkUnixTime + 9 * 3600).ToString(),
                        msg   = eqkStr,
                        scale = eqkMag.ToString("N1"),
                        mmi   = eqkIntens.ToString(),
                    };

                    return(new Message()
                    {
                        Level = Message.Priority.Critical,
                        Sender = "기상청 실시간 지진감시",
                        Text = buffer.ToString().TrimEnd(),
                    });
                }
                else if (phase == 3)
                {
                    // 분석 완료된 것 같고 깊이, 영향 지역이 나옴.

                    var buffer = new StringBuilder();
                    buffer.AppendLine("지진 상세정보가 발표되었습니다.");
                    buffer.AppendLine($"정보 : {eqkStr}");
                    buffer.AppendLine($"발생 시각 : {eqkTime:yyyy-MM-dd HH:mm:ss}");
                    buffer.AppendLine($"규모 : {eqkMag:N1}");
                    buffer.Append($"깊이 : ");
                    buffer.AppendLine((eqkDep == 0) ? "-" : $"{eqkDep} km");
                    buffer.AppendLine($"최대 진도 : {Earthquake.MMIToString(eqkIntens)}({eqkIntens})");
                    buffer.AppendLine($"영향 지역 : {string.Join(", ", eqkMaxArea)}");

                    m_prevAlarmId = alarmId;

                    return(new Message()
                    {
                        Level = Message.Priority.High,
                        Sender = "기상청 실시간 지진감시",
                        Text = buffer.ToString().TrimEnd(),
                    });
                }
            }

            return(msg);
        }
        public Message FormatTweet(Status tweet, Action <Message> sender)
        {
            StringBuilder alarmText = new StringBuilder(tweet.Text);


            var msgLevel = Message.Priority.Normal;

            var koreaKeywords = new string[]
            {
                "경북", "경남", "경기", "전남", "전북", "제주", "서울", "충남", "충북",
                "북도", "남도", "광역시", "특별",
                "부산", "대구", "인천", "광주", "대전", "울산", "세종", "강원",
            };

            // 트윗 내용이 한국 행정구역 키워드를 포함하고 있으면
            if (koreaKeywords.Any((text) => tweet.Text.Contains(text)))
            {
                msgLevel = Message.Priority.High;


                double scale = 0.0;

                if (tweet.Text.Contains("추정규모"))
                {
                    Regex rgx   = new Regex(@"규모\s*:\s*(\d{1,2}\.?\d*)");
                    var   match = rgx.Match(tweet.Text);
                    if (match.Success)
                    {
                        double.TryParse(match.Groups[1].ToString(), out scale);
                    }
                }
                else
                {
                    Regex rgx   = new Regex(@"규모\s?(\d{1,2}\.?\d*)");
                    var   match = rgx.Match(tweet.Text);
                    if (match.Success)
                    {
                        double.TryParse(match.Groups[1].ToString(), out scale);
                    }


                    Task.Factory.StartNew(SendImage, sender);
                }


                if (scale > 0)
                {
                    alarmText.AppendLine();
                    alarmText.AppendLine();

                    alarmText.Append(Earthquake.GetKnowHowFromMScale(scale));
                }
            }


            return(new Message()
            {
                Level = msgLevel,
                Text = $@"[기상청 지진정보서비스]
{alarmText.ToString()}",
            });
        }
Exemplo n.º 5
0
        protected override Message OnWork(Action <Message> sender)
        {
            try
            {
                int sampleCount = 0;

                lock (m_lockSampleCount)
                {
                    if (m_sampleCountList.Count > 0)
                    {
                        sampleCount = m_sampleCountList.Peek();
                    }
                }


                if (sampleCount > 0)
                {
                    bool runCheck = false;

                    lock (m_lockSamples)
                    {
                        // 현재까지 얻은 샘플 개수가 충분하면
                        if (m_samples.Count >= sampleCount)
                        {
                            runCheck = true;
                        }
                    }


                    if (runCheck)
                    {
                        lock (m_lockSampleCount)
                        {
                            m_sampleCountList.Dequeue();
                        }


                        /// Max Raw Data
                        double maxData      = -1;
                        int    maxDataIndex = -1;

                        /// 청크 샘플
                        List <double> subSamples = null;

                        lock (m_lockSamples)
                        {
                            // 파형 저장.
                            subSamples = Enumerable.Take(m_samples, sampleCount).ToList();


                            // 최댓값을 찾음.
                            int index = 0;
                            foreach (double data in subSamples)
                            {
                                double absData = Math.Abs(data);
                                if (absData > maxData)
                                {
                                    maxData      = absData;
                                    maxDataIndex = index;
                                }

                                ++index;
                            }


                            // 처리한 파형 제거.
                            m_samples.RemoveRange(0, sampleCount);
                        }


                        // 비동기로 데이터 수신 이벤트 발생.
                        if (WhenDataReceived != null)
                        {
                            Task.Factory.StartNew(delegate()
                            {
                                WhenDataReceived(this.Index, subSamples);
                            });
                        }


                        /// Max PGA or PGV
                        double groundValue = maxData / Gain;

                        // 측정값이 위험 수치를 넘어서면
                        if (groundValue > TriggerValue)
                        {
                            TriggerValue = groundValue;


                            // 분석 중인 경우
                            if (m_waveBuffer.Count > 0)
                            {
                                // 가장 최근 파형에 트리거 전까지의 데이터 추가
                                var lastWave = m_waveBuffer.Last();
                                lastWave.AddWave(subSamples.Take(maxDataIndex));
                            }

                            // 새 파형 생성
                            var newWave = new Wave()
                            {
                                EventTimeUtc = DateTime.UtcNow,
                                IsAccel      = IsAccel,
                                Length       = 1,
                                MaxValue     = groundValue,
                            };

                            // 이어지는 이전 파형 정보가 있다면 데이터 부분 이관.
                            if (m_prevWave != null)
                            {
                                newWave.EventTimeUtc = m_prevWave.EventTimeUtc;
                                newWave.Length      += m_prevWave.Length;
                                newWave.AddWaveToDraw(m_prevWave.TotalBuffer);
                            }

                            m_prevWave = newWave;

                            // 초기 데이터가 없어서 분석이 종료되는 불상사 방지
                            // 어차피 최대값 풀링 때문에 0은 무시된다.
                            newWave.AddWave(0);

                            // 트리거 이후의 데이터 추가
                            newWave.AddWave(subSamples.Skip(maxDataIndex + 1));

                            // 파형 그렸을 때 트리거 이전 부분도 보이면 좋으므로 전부 추가
                            newWave.AddWaveToDraw(subSamples);

                            m_waveBuffer.Add(newWave);
                        }
                        else if (m_waveBuffer.Count > 0)
                        {
                            var lastWave = m_waveBuffer.Last();
                            lastWave.AddWave(subSamples);
                            lastWave.AddWaveToDraw(subSamples);
                        }
                        else if (m_prevWave != null)
                        {
                            // 최종 파형의 분석이 완료됨.

                            var wave = m_prevWave;
                            m_prevWave = null;

                            double waveTime = (double)wave.Length / SamplingRate;

                            if (waveTime >= MinDangerWaveTime)
                            {
                                int mmi = 0;
                                if (IsAccel)
                                {
                                    mmi = Earthquake.ConvertPgaToMMI(wave.MaxValue);
                                }
                                else
                                {
                                    mmi = Earthquake.ConvertPgvToMMI(wave.MaxValue);
                                }

                                var msg = new Message()
                                {
                                    Level  = Message.Priority.Normal,
                                    Sender = Channel + " " + Network + "_" + Station + " Station",
                                    Text   = $@"{Name} {TypeText}의 진동에 관한 최종 분석 결과.
수치 : {(wave.MaxValue / DangerValue * 100.0).ToString("F2")}%
진도 : {Earthquake.MMIToString(mmi)}
지속시간 : 약 {string.Format("{0:F3}", waveTime)}초
지속시간이 매우 짧은 경우 오류일 확률이 높습니다.",
                                };

                                m_msgQueue.Enqueue(msg);


                                try
                                {
                                    string waveFile = SaveWaveToFile(wave);

                                    msg = new Message()
                                    {
                                        Level  = Message.Priority.Normal,
                                        Sender = "해석 : https://neurowhai.tistory.com/356",
                                        Text   = waveFile,
                                    };

                                    m_msgQueue.Enqueue(msg);
                                }
                                catch (Exception exp)
                                {
                                    Console.WriteLine(exp.Message);
                                    Console.WriteLine(exp.StackTrace);
                                }
                            }
                        }


                        for (int w = 0; w < m_waveBuffer.Count; ++w)
                        {
                            Wave wave = m_waveBuffer[w];


                            int stopIndex    = wave.BufferLength - WindowSize;
                            int checkedCount = 0;

                            for (int d = 0; d <= stopIndex; ++d)
                            {
                                ++checkedCount;

                                double max = wave.Buffer.Skip(d).Take(WindowSize)
                                             .Max((wav) => Math.Abs(wav));
                                double poolingValue = max / Gain;

                                // 안정화 되었거나 다른 파형이 나왔으면
                                if (poolingValue < NormalValue || poolingValue > wave.MaxValue)
                                {
                                    // 분석 종료
                                    checkedCount = wave.BufferLength;

                                    break;
                                }

                                ++wave.Length;
                            }

                            // 마지막 파형이 아니라면
                            if (w < m_waveBuffer.Count - 1)
                            {
                                // 분석 종료
                                checkedCount = wave.BufferLength;
                            }

                            // 처리한 데이터 제거
                            wave.RemoveWave(checkedCount);


                            double waveTime = (double)wave.Length / SamplingRate;

                            if (wave.IsDanger == false && waveTime > DangerWaveTime)
                            {
                                wave.IsDanger = true;


                                int mmi = 0;
                                if (IsAccel)
                                {
                                    mmi = Earthquake.ConvertPgaToMMI(wave.MaxValue);
                                }
                                else
                                {
                                    mmi = Earthquake.ConvertPgvToMMI(wave.MaxValue);
                                }

                                var msg = new Message()
                                {
                                    Level  = Message.Priority.Critical,
                                    Sender = Channel + " " + Network + "_" + Station + " Station",
                                    Text   = $@"⚠️ {Name} {TypeText}의 진동에 관한 조기 분석 결과.
수치 : {(wave.MaxValue / DangerValue * 100.0).ToString("F2")}%
진도 : {Earthquake.MMIToString(mmi)}
지속시간 : 약 {string.Format("{0:F3}", waveTime)}초 이상

{Earthquake.GetKnowHowFromMMI(mmi)}",
                                };

                                m_msgQueue.Enqueue(msg);
                            }


                            // 분석이 종료되었으면
                            if (wave.BufferLength <= 0)
                            {
                                // 파형 제거
                                m_waveBuffer.RemoveAt(w);
                                --w;
                            }
                        }


                        if (m_waveBuffer.Count <= 0)
                        {
                            // 트리거 기준 리셋
                            TriggerValue = DangerValue;


                            // 평소 수치 계산
                            NormalValue = subSamples
                                          .Select((wav) => Math.Abs(wav) / Gain)
                                          .Max();
                            NormalValue *= 2;

                            NormalValue = Math.Max(NormalValue, MinNormalValue);
                            NormalValue = Math.Min(NormalValue, MaxNormalValue);
                        }
                    }
                }
            }
            catch (Exception exp)
            {
                Console.WriteLine(exp.Message);
                Console.WriteLine(exp.StackTrace);
            }


            if (m_msgQueue.Count > 0)
            {
                m_logger.PushLog(m_msgQueue.Peek());


                return(m_msgQueue.Dequeue());
            }
            else
            {
                return(null);
            }
        }
        protected override Message OnWork(Action <Message> sender)
        {
            try
            {
                var korTime = DateTime.UtcNow + TimeSpan.FromHours(9); // KST

                var uri = new StringBuilder(@"http://necis.kma.go.kr/necis-dbf/usernl/earthquake/earthquakeForAlertList.do?selectDate=custom");
                uri.Append("&startDate=");
                uri.Append((korTime - TimeSpan.FromMinutes(1)).ToString("yyyy-MM-dd"));
                uri.Append("&endDate=");
                uri.Append(korTime.ToString("yyyy-MM-dd"));
                uri.Append("&startMagnitude=0.0&endMagnitude=9.9");

                var task = m_client.GetByteArrayAsync(uri.ToString());

                task.Wait();


                var byteArray = task.Result.ToArray();
                var encoding  = Encoding.GetEncoding(65001 /*utf-8*/);
                var html      = encoding.GetString(byteArray, 0, byteArray.Length);

                if (string.IsNullOrWhiteSpace(html) == false)
                {
                    int beginIndex = html.IndexOf("value", html.IndexOf("totCnt") + 1);
                    beginIndex = html.IndexOf("\"", beginIndex + 1);

                    int endIndex = html.IndexOf("\"", beginIndex + 1);

                    if (int.TryParse(html.Substring(beginIndex + 1, endIndex - beginIndex - 1), out int count))
                    {
                        if (m_latestCount < 0 || m_latestCount > count)
                        {
                            m_latestCount = count;
                        }


                        if (count > m_latestCount)
                        {
                            m_latestCount = count;


                            // Warning

                            string warningTime = "";
                            string lati        = "";
                            string longi       = "";
                            double magnitude   = -1;
                            string location    = "";

                            beginIndex = html.IndexOf("</thead>", beginIndex);

                            var matches = Regex.Matches(html.Substring(beginIndex + 1),
                                                        @"<td>(.*)<\/td>");

                            if (matches.Count >= 5)
                            {
                                warningTime = matches[1].Groups[1].Value;
                                lati        = matches[2].Groups[1].Value;
                                longi       = matches[3].Groups[1].Value;
                                double.TryParse(matches[4].Groups[1].Value, out magnitude);

                                var match = Regex.Match(html.Substring(matches[4].Index),
                                                        "<td class=\".+\">(.*)<\\/td>");
                                if (match.Success)
                                {
                                    location = match.Groups[1].Value;
                                }
                            }


                            if (magnitude > 0 && magnitude < 13)
                            {
                                ConvertAngle(double.Parse(lati), out int latiD, out int latiM, out double latiS);
                                ConvertAngle(double.Parse(longi), out int longiD, out int longiM, out double longiS);

                                var mapLink = new StringBuilder("https://www.google.com/maps/place/");
                                mapLink.Append(latiD + "°" + latiM + "\'" + latiS + "%22N+");
                                mapLink.Append(longiD + "°" + longiM + "\'" + longiS + "%22E/@");
                                mapLink.Append(lati + ",");
                                mapLink.Append(longi + ",7z");

                                var msg = new StringBuilder();
                                msg.AppendLine(warningTime);
                                msg.AppendLine("⚠️ 지진조기경보가 발표되었습니다.");
                                msg.AppendLine("규모 : " + magnitude);
                                msg.AppendLine("지역 : " + location);
                                msg.AppendLine("진앙 : " + mapLink.ToString());
                                msg.AppendLine();
                                msg.AppendLine(Earthquake.GetKnowHowFromMScale(magnitude));

                                return(new Message()
                                {
                                    Sender = "NECIS 지진조기경보",
                                    Level = Message.Priority.Critical,
                                    Text = msg.ToString(),
                                });
                            }
                        }


                        Console.Write("@");
                    }
                }
            }
            catch (Exception exp)
            {
                Console.WriteLine(exp.Message);
                Console.WriteLine(exp.StackTrace);


                Thread.Sleep(10000);

                m_client = new HttpClient();
            }


            return(null);
        }