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); }
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); }
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()}", }); }
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); }